summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-01 21:13:05 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-01 21:13:05 +0000
commit58acbd41a1ee5aa51777f2ef88ce03bd698530c7 (patch)
tree3dc36a5296cf53123f494a49892cbb8267d31907
parentad1e76fb4d1392c890c8b5e218a256a416d5a50b (diff)
downloadgitlab-ce-58acbd41a1ee5aa51777f2ef88ce03bd698530c7.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml9
-rw-r--r--.gitlab/ci/notify.gitlab-ci.yml25
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml9
-rw-r--r--.rubocop_todo/rake/require.yml2
-rw-r--r--app/assets/javascripts/analytics/cycle_analytics/constants.js5
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue8
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue72
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue43
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/constants.js5
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/graphql/queries/group_variables.query.graphql11
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/graphql/queries/project_variables.query.graphql11
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/graphql/queries/variables.query.graphql10
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/graphql/settings.js49
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/index.js9
-rw-r--r--app/assets/javascripts/ci/reports/constants.js14
-rw-r--r--app/assets/javascripts/ci/runner/constants.js1
-rw-r--r--app/assets/javascripts/clusters/constants.js16
-rw-r--r--app/assets/javascripts/diffs/constants.js8
-rw-r--r--app/assets/javascripts/editor/constants.js3
-rw-r--r--app/assets/javascripts/feature_flags/constants.js8
-rw-r--r--app/assets/javascripts/groups/constants.js1
-rw-r--r--app/assets/javascripts/header_search/constants.js4
-rw-r--r--app/assets/javascripts/invite_members/constants.js1
-rw-r--r--app/assets/javascripts/issuable/components/status_box.vue6
-rw-r--r--app/assets/javascripts/issues/constants.js1
-rw-r--r--app/assets/javascripts/jobs/components/table/constants.js1
-rw-r--r--app/assets/javascripts/members/constants.js2
-rw-r--r--app/assets/javascripts/monitoring/constants.js6
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js11
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/constants.js4
-rw-r--r--app/assets/javascripts/pipelines/constants.js3
-rw-r--r--app/assets/javascripts/protected_branches/constants.js9
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js1
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/milestone/milestone_dropdown.vue9
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue4
-rw-r--r--app/assets/javascripts/sidebar/constants.js23
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js26
-rw-r--r--app/assets/javascripts/usage_quotas/storage/constants.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/constants.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/constants.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_select/user_select.vue4
-rw-r--r--app/assets/javascripts/vue_shared/constants.js2
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb5
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb4
-rw-r--r--app/models/clusters/applications/crossplane.rb58
-rw-r--r--app/models/clusters/cluster.rb2
-rw-r--r--config/feature_flags/development/ci_variables_pages.yml (renamed from config/feature_flags/development/ci_use_downstream_pipeline_duration_for_calculation.yml)12
-rw-r--r--db/docs/clusters_applications_crossplane.yml2
-rw-r--r--doc/development/fe_guide/widgets.md4
-rw-r--r--doc/development/fips_compliance.md83
-rw-r--r--doc/integration/jira/configure.md4
-rw-r--r--doc/integration/jira/development_panel.md80
-rw-r--r--doc/integration/jira/index.md2
-rw-r--r--doc/topics/awesome_co.md70
-rw-r--r--doc/user/application_security/secret_detection/index.md11
-rw-r--r--doc/user/group/reporting/git_abuse_rate_limit.md2
-rw-r--r--lib/api/search.rb3
-rw-r--r--lib/generators/gitlab/snowplow_event_definition_generator.rb4
-rw-r--r--lib/gitlab/ci/pipeline/duration.rb7
-rw-r--r--lib/gitlab/search_results.rb8
-rw-r--r--lib/tasks/gitlab/x509/update.rake4
-rw-r--r--lib/tasks/import.rake6
-rw-r--r--locale/gitlab.pot15
-rw-r--r--package.json70
-rw-r--r--scripts/api/update_issue.rb29
-rwxr-xr-xscripts/pipeline/create_test_failure_issues.rb224
-rw-r--r--spec/factories/clusters/applications/helm.rb5
-rw-r--r--spec/factories/clusters/clusters.rb1
-rw-r--r--spec/features/admin_variables_spec.rb14
-rw-r--r--spec/features/group_variables_spec.rb13
-rw-r--r--spec/features/issuables/issuable_list_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb13
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js20
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js584
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js189
-rw-r--r--spec/frontend/commit/pipelines/pipelines_table_spec.js114
-rw-r--r--spec/frontend/emoji/components/emoji_list_spec.js33
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js6
-rw-r--r--spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/user_select_spec.js4
-rw-r--r--spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/duration_spec.rb48
-rw-r--r--spec/models/clusters/applications/crossplane_spec.rb62
-rw-r--r--spec/requests/api/search_spec.rb17
-rw-r--r--spec/scripts/pipeline/create_test_failure_issues_spec.rb145
-rw-r--r--spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb66
-rw-r--r--yarn.lock366
101 files changed, 1706 insertions, 1212 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 29e51b25200..dd23c648bd4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -36,11 +36,12 @@ default:
OMNIBUS_GITLAB_RUBY2_BUILD: "true"
OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY2"
-.default-branch-incident-variables: &default-branch-incident-variables
+.default-branch-pipeline-failure-variables: &default-branch-pipeline-failure-variables
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
NOTIFY_PIPELINE_FAILURE_CHANNEL: "master-broken"
BROKEN_BRANCH_INCIDENTS_PROJECT: "gitlab-org/quality/engineering-productivity/master-broken-incidents"
BROKEN_BRANCH_INCIDENTS_PROJECT_TOKEN: "${BROKEN_MASTER_INCIDENTS_PROJECT_TOKEN}"
+ CREATE_ISSUES_FOR_FAILING_TESTS: "true"
workflow:
name: '$PIPELINE_NAME'
@@ -76,7 +77,7 @@ workflow:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule"'
variables:
<<: *ruby3-variables
- <<: *default-branch-incident-variables
+ <<: *default-branch-pipeline-failure-variables
CRYSTALBALL: "true"
PIPELINE_NAME: 'Scheduled Ruby 3 $CI_COMMIT_BRANCH branch pipeline'
# Run pipelines for ruby2 branch
@@ -90,14 +91,14 @@ workflow:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $GITLAB_USER_LOGIN =~ /project_\d+_bot\d*/'
variables:
<<: *ruby3-variables
- <<: *default-branch-incident-variables
+ <<: *default-branch-pipeline-failure-variables
GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
PIPELINE_NAME: 'Ruby 3 $CI_COMMIT_BRANCH branch pipeline (triggered by a project token)'
# For `$CI_DEFAULT_BRANCH` branch, create a pipeline (this includes on schedules, pushes, merges, etc.).
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
variables:
<<: *ruby3-variables
- <<: *default-branch-incident-variables
+ <<: *default-branch-pipeline-failure-variables
PIPELINE_NAME: 'Ruby 3 $CI_COMMIT_BRANCH branch pipeline'
# For tags, create a pipeline.
- if: '$CI_COMMIT_TAG'
diff --git a/.gitlab/ci/notify.gitlab-ci.yml b/.gitlab/ci/notify.gitlab-ci.yml
index 20f19978022..795a0cd6439 100644
--- a/.gitlab/ci/notify.gitlab-ci.yml
+++ b/.gitlab/ci/notify.gitlab-ci.yml
@@ -70,3 +70,28 @@ notify-pipeline-failure:
- ${FAILED_PIPELINE_SLACK_MESSAGE_FILE}
when: always
expire_in: 2 days
+
+create-issues-for-failing-tests:
+ extends:
+ - .notify-defaults
+ - .notify:rules:create-issues-for-failing-tests
+ image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}
+ variables:
+ FAILED_TESTS_DIR: "${CI_PROJECT_DIR}/tmp/failed_tests"
+ FAILING_ISSUES_PROJECT: "gitlab-org/quality/engineering-productivity/flaky-tests-playground"
+ FAILING_ISSUE_JSON_DIR: "${CI_PROJECT_DIR}/tmp/issues"
+ before_script:
+ - source ./scripts/utils.sh
+ - source ./scripts/rspec_helpers.sh
+ - install_gitlab_gem
+ script:
+ - mkdir -p "${FAILING_ISSUE_JSON_DIR}"
+ - retrieve_failed_tests "${FAILED_TESTS_DIR}" "json" "latest"
+ - scripts/pipeline/create_test_failure_issues.rb --project "${FAILING_ISSUES_PROJECT}" --tests-report-file "${FAILED_TESTS_DIR}/rspec_failed_tests.json" --issues-json-folder "${FAILING_ISSUE_JSON_DIR}" --api-token "${FAILING_ISSUES_PROJECT_TOKEN}"
+ - scripts/pipeline/create_test_failure_issues.rb --project "${FAILING_ISSUES_PROJECT}" --tests-report-file "${FAILED_TESTS_DIR}/rspec_ee_failed_tests.json" --issues-json-folder "${FAILING_ISSUE_JSON_DIR}" --api-token "${FAILING_ISSUES_PROJECT_TOKEN}"
+ artifacts:
+ paths:
+ - ${FAILED_TESTS_DIR}/
+ - ${FAILING_ISSUE_JSON_DIR}/
+ when: always
+ expire_in: 2 days
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 066654565b2..69e846440c3 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1105,6 +1105,15 @@
when: on_failure
allow_failure: true
+.notify:rules:create-issues-for-failing-tests:
+ rules:
+ # Don't report child pipeline failures
+ - if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
+ when: never
+ - if: '$CREATE_ISSUES_FOR_FAILING_TESTS == "true"'
+ when: on_failure
+ allow_failure: true
+
###############
# Pages rules #
###############
diff --git a/.rubocop_todo/rake/require.yml b/.rubocop_todo/rake/require.yml
index 11c645d682a..339a2018d19 100644
--- a/.rubocop_todo/rake/require.yml
+++ b/.rubocop_todo/rake/require.yml
@@ -11,7 +11,5 @@ Rake/Require:
- 'lib/tasks/gitlab/packages/migrate.rake'
- 'lib/tasks/gitlab/pages.rake'
- 'lib/tasks/gitlab/refresh_project_statistics_build_artifacts_size.rake'
- - 'lib/tasks/gitlab/x509/update.rake'
- - 'lib/tasks/import.rake'
- 'lib/tasks/tokens.rake'
- 'qa/tasks/webdrivers.rake'
diff --git a/app/assets/javascripts/analytics/cycle_analytics/constants.js b/app/assets/javascripts/analytics/cycle_analytics/constants.js
index 2758d686fb1..0bf4d0ef8c5 100644
--- a/app/assets/javascripts/analytics/cycle_analytics/constants.js
+++ b/app/assets/javascripts/analytics/cycle_analytics/constants.js
@@ -32,11 +32,6 @@ export const I18N_VSA_ERROR_SELECTED_STAGE = __(
'There was an error fetching data for the selected stage',
);
-export const OVERVIEW_METRICS = {
- TIME_SUMMARY: 'TIME_SUMMARY',
- RECENT_ACTIVITY: 'RECENT_ACTIVITY',
-};
-
export const SUMMARY_METRICS_REQUEST = [
{ endpoint: METRIC_TYPE_SUMMARY, name: __('recent activity'), request: getValueStreamMetrics },
];
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue
index 3c6114b38ce..257c3309e10 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue
@@ -38,6 +38,10 @@ export default {
required: false,
default: 0,
},
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
variables: {
type: Array,
required: true,
@@ -87,8 +91,12 @@ export default {
:entity="entity"
:is-loading="isLoading"
:max-variable-limit="maxVariableLimit"
+ :page-info="pageInfo"
:variables="variables"
+ @handle-prev-page="$emit('handle-prev-page')"
+ @handle-next-page="$emit('handle-next-page')"
@set-selected-variable="setSelectedVariable"
+ @sort-changed="(val) => $emit('sort-changed', val)"
/>
<ci-variable-modal
v-if="showModal"
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue
index 6e39bda0b07..9c3580a95fe 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue
@@ -1,10 +1,12 @@
<script>
import { createAlert } from '~/flash';
import { __ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { mapEnvironmentNames, reportMessageToSentry } from '../utils';
import {
ADD_MUTATION_ACTION,
DELETE_MUTATION_ACTION,
+ SORT_DIRECTIONS,
UPDATE_MUTATION_ACTION,
environmentFetchErrorText,
genericMutationErrorText,
@@ -16,6 +18,7 @@ export default {
components: {
CiVariableSettings,
},
+ mixins: [glFeatureFlagsMixin()],
inject: ['endpoint'],
props: {
areScopedVariablesAvailable: {
@@ -97,6 +100,7 @@ export default {
loadingCounter: 0,
maxVariableLimit: 0,
pageInfo: {},
+ sortDirection: SORT_DIRECTIONS.ASC,
};
},
apollo: {
@@ -107,6 +111,8 @@ export default {
variables() {
return {
fullPath: this.fullPath || undefined,
+ first: this.pageSize,
+ sort: this.sortDirection,
};
},
update(data) {
@@ -116,21 +122,23 @@ export default {
this.maxVariableLimit = this.queryData.ciVariables.lookup(data)?.limit || 0;
this.pageInfo = this.queryData.ciVariables.lookup(data)?.pageInfo || this.pageInfo;
- this.hasNextPage = this.pageInfo?.hasNextPage || false;
- // Because graphQL has a limit of 100 items,
- // we batch load all the variables by making successive queries
- // to keep the same UX. As a safeguard, we make sure that we cannot go over
- // 20 consecutive API calls, which means 2000 variables loaded maximum.
- if (!this.hasNextPage) {
- this.isLoadingMoreItems = false;
- } else if (this.loadingCounter < 20) {
- this.hasNextPage = false;
- this.fetchMoreVariables();
- this.loadingCounter += 1;
- } else {
- createAlert({ message: this.$options.tooManyCallsError });
- reportMessageToSentry(this.componentName, this.$options.tooManyCallsError, {});
+ if (!this.glFeatures?.ciVariablesPages) {
+ this.hasNextPage = this.pageInfo?.hasNextPage || false;
+ // Because graphQL has a limit of 100 items,
+ // we batch load all the variables by making successive queries
+ // to keep the same UX. As a safeguard, we make sure that we cannot go over
+ // 20 consecutive API calls, which means 2000 variables loaded maximum.
+ if (!this.hasNextPage) {
+ this.isLoadingMoreItems = false;
+ } else if (this.loadingCounter < 20) {
+ this.hasNextPage = false;
+ this.fetchMoreVariables();
+ this.loadingCounter += 1;
+ } else {
+ createAlert({ message: this.$options.tooManyCallsError });
+ reportMessageToSentry(this.componentName, this.$options.tooManyCallsError, {});
+ }
}
},
error() {
@@ -172,6 +180,9 @@ export default {
this.isLoadingMoreItems
);
},
+ pageSize() {
+ return this.glFeatures?.ciVariablesPages ? 20 : 100;
+ },
},
methods: {
addVariable(variable) {
@@ -189,6 +200,31 @@ export default {
},
});
},
+ handlePrevPage() {
+ this.$apollo.queries.ciVariables.fetchMore({
+ variables: {
+ before: this.pageInfo.startCursor,
+ first: null,
+ last: this.pageSize,
+ },
+ });
+ },
+ handleNextPage() {
+ this.$apollo.queries.ciVariables.fetchMore({
+ variables: {
+ after: this.pageInfo.endCursor,
+ first: this.pageSize,
+ last: null,
+ },
+ });
+ },
+ async handleSortChanged({ sortDesc }) {
+ this.sortDirection = sortDesc ? SORT_DIRECTIONS.DESC : SORT_DIRECTIONS.ASC;
+
+ // Wait for the new sort direction to be updated and then refetch
+ await this.$nextTick();
+ this.$apollo.queries.ciVariables.refetch();
+ },
updateVariable(variable) {
this.variableMutation(UPDATE_MUTATION_ACTION, variable);
},
@@ -230,13 +266,17 @@ export default {
<ci-variable-settings
:are-scoped-variables-available="areScopedVariablesAvailable"
:entity="entity"
+ :environments="environments"
:hide-environment-scope="hideEnvironmentScope"
:is-loading="isLoading"
- :variables="ciVariables"
:max-variable-limit="maxVariableLimit"
- :environments="environments"
+ :page-info="pageInfo"
+ :variables="ciVariables"
@add-variable="addVariable"
@delete-variable="deleteVariable"
+ @handle-prev-page="handlePrevPage"
+ @handle-next-page="handleNextPage"
+ @sort-changed="handleSortChanged"
@update-variable="updateVariable"
/>
</template>
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
index 345a8def49d..5e367ff33b2 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
@@ -4,6 +4,7 @@ import {
GlButton,
GlLoadingIcon,
GlModalDirective,
+ GlKeysetPagination,
GlTable,
GlTooltipDirective,
} from '@gitlab/ui';
@@ -56,6 +57,7 @@ export default {
components: {
GlAlert,
GlButton,
+ GlKeysetPagination,
GlLoadingIcon,
GlTable,
},
@@ -78,6 +80,10 @@ export default {
type: Number,
required: true,
},
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
variables: {
type: Array,
required: true,
@@ -165,6 +171,28 @@ export default {
>
{{ exceedsVariableLimitText }}
</gl-alert>
+ <div
+ v-if="glFeatures.ciVariablesPages"
+ class="ci-variable-actions gl-display-flex gl-justify-content-end gl-my-3"
+ >
+ <gl-button
+ v-if="!isTableEmpty"
+ data-qa-selector="reveal_ci_variable_value_button"
+ @click="toggleHiddenState"
+ >{{ valuesButtonText }}</gl-button
+ >
+ <gl-button
+ v-gl-modal-directive="$options.modalId"
+ class="gl-mx-3"
+ data-qa-selector="add_ci_variable_button"
+ variant="confirm"
+ category="primary"
+ :aria-label="__('Add')"
+ :disabled="exceedsVariableLimit"
+ @click="setSelectedVariable()"
+ >{{ __('Add variable') }}</gl-button
+ >
+ </div>
<gl-table
v-if="!isLoading"
:fields="fields"
@@ -174,11 +202,13 @@ export default {
sort-by="key"
sort-direction="asc"
stacked="lg"
- table-class="text-secondary"
+ table-class="gl-border-t"
fixed
show-empty
sort-icon-left
no-sort-reset
+ no-local-sorting
+ @sort-changed="(val) => $emit('sort-changed', val)"
>
<template #table-colgroup="scope">
<col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" />
@@ -275,7 +305,7 @@ export default {
>
{{ exceedsVariableLimitText }}
</gl-alert>
- <div class="ci-variable-actions gl-display-flex gl-mt-5">
+ <div v-if="!glFeatures.ciVariablesPages" class="ci-variable-actions gl-display-flex gl-mt-5">
<gl-button
v-gl-modal-directive="$options.modalId"
class="gl-mr-3"
@@ -294,5 +324,14 @@ export default {
>{{ valuesButtonText }}</gl-button
>
</div>
+ <div v-else class="gl-display-flex gl-justify-content-center gl-mt-6">
+ <gl-keyset-pagination
+ v-bind="pageInfo"
+ :prev-text="__('Previous')"
+ :next-text="__('Next')"
+ @prev="$emit('handle-prev-page')"
+ @next="$emit('handle-next-page')"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/ci/ci_variable_list/constants.js b/app/assets/javascripts/ci/ci_variable_list/constants.js
index 627ace1b28e..c77d8c67bc8 100644
--- a/app/assets/javascripts/ci/ci_variable_list/constants.js
+++ b/app/assets/javascripts/ci/ci_variable_list/constants.js
@@ -2,6 +2,11 @@ import { __, s__ } from '~/locale';
export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable';
+export const SORT_DIRECTIONS = {
+ ASC: 'KEY_ASC',
+ DESC: 'KEY_DESC',
+};
+
// This const will be deprecated once we remove VueX from the section
export const displayText = {
variableText: __('Variable'),
diff --git a/app/assets/javascripts/ci/ci_variable_list/graphql/queries/group_variables.query.graphql b/app/assets/javascripts/ci/ci_variable_list/graphql/queries/group_variables.query.graphql
index 538502fdd3b..4a64a24573e 100644
--- a/app/assets/javascripts/ci/ci_variable_list/graphql/queries/group_variables.query.graphql
+++ b/app/assets/javascripts/ci/ci_variable_list/graphql/queries/group_variables.query.graphql
@@ -1,10 +1,17 @@
#import "~/ci/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
-query getGroupVariables($after: String, $first: Int = 100, $fullPath: ID!) {
+query getGroupVariables(
+ $after: String
+ $before: String
+ $first: Int
+ $fullPath: ID!
+ $last: Int
+ $sort: CiVariableSort = KEY_ASC
+) {
group(fullPath: $fullPath) {
id
- ciVariables(after: $after, first: $first) {
+ ciVariables(after: $after, before: $before, first: $first, last: $last, sort: $sort) {
limit
pageInfo {
...PageInfo
diff --git a/app/assets/javascripts/ci/ci_variable_list/graphql/queries/project_variables.query.graphql b/app/assets/javascripts/ci/ci_variable_list/graphql/queries/project_variables.query.graphql
index af0cd2d0b2c..03a7142080b 100644
--- a/app/assets/javascripts/ci/ci_variable_list/graphql/queries/project_variables.query.graphql
+++ b/app/assets/javascripts/ci/ci_variable_list/graphql/queries/project_variables.query.graphql
@@ -1,10 +1,17 @@
#import "~/ci/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
-query getProjectVariables($after: String, $first: Int = 100, $fullPath: ID!) {
+query getProjectVariables(
+ $after: String
+ $before: String
+ $first: Int
+ $fullPath: ID!
+ $last: Int
+ $sort: CiVariableSort = KEY_ASC
+) {
project(fullPath: $fullPath) {
id
- ciVariables(after: $after, first: $first) {
+ ciVariables(after: $after, before: $before, first: $first, last: $last, sort: $sort) {
limit
pageInfo {
...PageInfo
diff --git a/app/assets/javascripts/ci/ci_variable_list/graphql/queries/variables.query.graphql b/app/assets/javascripts/ci/ci_variable_list/graphql/queries/variables.query.graphql
index b8dd6f5f562..adf539a44ae 100644
--- a/app/assets/javascripts/ci/ci_variable_list/graphql/queries/variables.query.graphql
+++ b/app/assets/javascripts/ci/ci_variable_list/graphql/queries/variables.query.graphql
@@ -1,8 +1,14 @@
#import "~/ci/ci_variable_list/graphql/fragments/ci_variable.fragment.graphql"
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
-query getVariables($after: String, $first: Int = 100) {
- ciVariables(after: $after, first: $first) {
+query getVariables(
+ $after: String
+ $before: String
+ $first: Int
+ $last: Int
+ $sort: CiVariableSort = KEY_ASC
+) {
+ ciVariables(after: $after, before: $before, first: $first, last: $last, sort: $sort) {
pageInfo {
...PageInfo
}
diff --git a/app/assets/javascripts/ci/ci_variable_list/graphql/settings.js b/app/assets/javascripts/ci/ci_variable_list/graphql/settings.js
index cafe3df35d0..7ed0418d5f4 100644
--- a/app/assets/javascripts/ci/ci_variable_list/graphql/settings.js
+++ b/app/assets/javascripts/ci/ci_variable_list/graphql/settings.js
@@ -205,33 +205,40 @@ export const mergeVariables = (existing, incoming, { args }) => {
return result;
};
-export const cacheConfig = {
- cacheConfig: {
- typePolicies: {
- Query: {
- fields: {
- ciVariables: {
- keyArgs: false,
- merge: mergeVariables,
+export const mergeOnlyIncomings = (_, incoming) => {
+ return incoming;
+};
+
+export const generateCacheConfig = (isVariablePagesEnabled = false) => {
+ const merge = isVariablePagesEnabled ? mergeOnlyIncomings : mergeVariables;
+ return {
+ cacheConfig: {
+ typePolicies: {
+ Query: {
+ fields: {
+ ciVariables: {
+ keyArgs: false,
+ merge,
+ },
},
},
- },
- Project: {
- fields: {
- ciVariables: {
- keyArgs: ['fullPath', 'endpoint', 'id'],
- merge: mergeVariables,
+ Project: {
+ fields: {
+ ciVariables: {
+ keyArgs: ['fullPath'],
+ merge,
+ },
},
},
- },
- Group: {
- fields: {
- ciVariables: {
- keyArgs: ['fullPath'],
- merge: mergeVariables,
+ Group: {
+ fields: {
+ ciVariables: {
+ keyArgs: ['fullPath'],
+ merge,
+ },
},
},
},
},
- },
+ };
};
diff --git a/app/assets/javascripts/ci/ci_variable_list/index.js b/app/assets/javascripts/ci/ci_variable_list/index.js
index 4270c3c67fc..3ed56201f0d 100644
--- a/app/assets/javascripts/ci/ci_variable_list/index.js
+++ b/app/assets/javascripts/ci/ci_variable_list/index.js
@@ -5,7 +5,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import CiAdminVariables from './components/ci_admin_variables.vue';
import CiGroupVariables from './components/ci_group_variables.vue';
import CiProjectVariables from './components/ci_project_variables.vue';
-import { cacheConfig, resolvers } from './graphql/settings';
+import { generateCacheConfig, resolvers } from './graphql/settings';
const mountCiVariableListApp = (containerEl) => {
const {
@@ -42,8 +42,13 @@ const mountCiVariableListApp = (containerEl) => {
Vue.use(VueApollo);
+ // If the feature flag `ci_variables_pages` is enabled,
+ // we are using the default cache config with pages.
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(resolvers, cacheConfig),
+ defaultClient: createDefaultClient(
+ resolvers,
+ generateCacheConfig(window.gon?.features?.ciVariablesPages),
+ ),
});
return new Vue({
diff --git a/app/assets/javascripts/ci/reports/constants.js b/app/assets/javascripts/ci/reports/constants.js
index bad6fa1e7b9..1137236d355 100644
--- a/app/assets/javascripts/ci/reports/constants.js
+++ b/app/assets/javascripts/ci/reports/constants.js
@@ -1,10 +1,3 @@
-export const fieldTypes = {
- codeBlock: 'codeBlock',
- link: 'link',
- seconds: 'seconds',
- text: 'text',
-};
-
export const LOADING = 'LOADING';
export const ERROR = 'ERROR';
export const SUCCESS = 'SUCCESS';
@@ -15,10 +8,6 @@ export const STATUS_NEUTRAL = 'neutral';
export const STATUS_NOT_FOUND = 'not_found';
export const ICON_WARNING = 'warning';
-export const ICON_SUCCESS = 'success';
-export const ICON_NOTFOUND = 'notfound';
-export const ICON_PENDING = 'pending';
-export const ICON_FAILED = 'failed';
export const status = {
LOADING,
@@ -26,9 +15,6 @@ export const status = {
SUCCESS,
};
-export const ACCESSIBILITY_ISSUE_ERROR = 'error';
-export const ACCESSIBILITY_ISSUE_WARNING = 'warning';
-
/**
* Slot names for the ReportSection component, corresponding to the success,
* loading and error statuses.
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 1db4ff68872..054e6b954d0 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -105,7 +105,6 @@ export const I18N_JOBS = s__('Runners|Jobs');
export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})');
export const I18N_FILTER_PROJECTS = s__('Runners|Filter projects');
export const I18N_CLEAR_FILTER_PROJECTS = __('Clear');
-export const I18N_NONE = __('None');
export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.');
export const I18N_NO_PROJECTS_FOUND = __('No projects found');
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
deleted file mode 100644
index c6ca895778d..00000000000
--- a/app/assets/javascripts/clusters/constants.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// These need to match the enum found in app/models/clusters/cluster.rb
-export const CLUSTER_TYPE = {
- INSTANCE: 'instance_type',
- GROUP: 'group_type',
- PROJECT: 'project_type',
-};
-
-// These need to match the available providers in app/models/clusters/providers/
-export const PROVIDER_TYPE = {
- GCP: 'gcp',
-};
-
-// These are only used client-side
-
-export const LOGGING_MODE = 'logging';
-export const BLOCKING_MODE = 'blocking';
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 6c0c9c4e1d0..873c4819669 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -6,10 +6,8 @@ export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline';
export const NEW_NO_NEW_LINE_TYPE = 'new-nonewline';
export const CONTEXT_LINE_TYPE = 'context';
export const EMPTY_CELL_TYPE = 'empty-cell';
-export const COMMENT_FORM_TYPE = 'commentForm';
export const DIFF_NOTE_TYPE = 'DiffNote';
export const LEGACY_DIFF_NOTE_TYPE = 'LegacyDiffNote';
-export const NOTE_TYPE = 'Note';
export const NEW_LINE_TYPE = 'new';
export const OLD_LINE_TYPE = 'old';
export const TEXT_DIFF_POSITION_TYPE = 'text';
@@ -17,14 +15,10 @@ export const IMAGE_DIFF_POSITION_TYPE = 'image';
export const LINE_POSITION_LEFT = 'left';
export const LINE_POSITION_RIGHT = 'right';
-export const LINE_SIDE_LEFT = 'left-side';
-export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
export const DIFF_WHITESPACE_COOKIE_NAME = 'diff_whitespace';
export const LINE_HOVER_CLASS_NAME = 'is-over';
-export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold';
-export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
@@ -46,14 +40,12 @@ export const TREE_HIDE_STATS_WIDTH = 260;
export const OLD_LINE_KEY = 'old_line';
export const NEW_LINE_KEY = 'new_line';
export const TYPE_KEY = 'type';
-export const LEFT_LINE_KEY = 'left';
export const MAX_RENDERING_DIFF_LINES = 500;
export const MAX_RENDERING_BULK_ROWS = 30;
export const MIN_RENDERING_MS = 2;
export const START_RENDERING_INDEX = 200;
export const INLINE_DIFF_LINES_KEY = 'highlighted_diff_lines';
-export const PARALLEL_DIFF_LINES_KEY = 'parallel_diff_lines';
export const DIFF_COMPARE_BASE_VERSION_INDEX = -1;
export const DIFF_COMPARE_HEAD_VERSION_INDEX = -2;
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 2a47eef148e..df5571d0872 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -21,9 +21,6 @@ export const EDITOR_TOOLBAR_RIGHT_GROUP = 'right';
export const SOURCE_EDITOR_INSTANCE_ERROR_NO_EL = s__(
'SourceEditor|"el" parameter is required for createInstance()',
);
-export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = s__(
- 'SourceEditor|Source Editor instance is required to set up an extension.',
-);
export const EDITOR_EXTENSION_DEFINITION_ERROR = s__(
'SourceEditor|Extension definition should be either a class or a function',
);
diff --git a/app/assets/javascripts/feature_flags/constants.js b/app/assets/javascripts/feature_flags/constants.js
index f697f203cf5..1993ec7abf2 100644
--- a/app/assets/javascripts/feature_flags/constants.js
+++ b/app/assets/javascripts/feature_flags/constants.js
@@ -1,4 +1,3 @@
-import { property } from 'lodash';
import { s__ } from '~/locale';
export const ROLLOUT_STRATEGY_ALL_USERS = 'default';
@@ -9,15 +8,8 @@ export const ROLLOUT_STRATEGY_GITLAB_USER_LIST = 'gitlabUserList';
export const PERCENT_ROLLOUT_GROUP_ID = 'default';
-export const DEFAULT_PERCENT_ROLLOUT = '100';
-
export const ALL_ENVIRONMENTS_NAME = '*';
-export const INTERNAL_ID_PREFIX = 'internal_';
-
-export const fetchPercentageParams = property(['parameters', 'percentage']);
-export const fetchUserIdParams = property(['parameters', 'userIds']);
-
export const NEW_VERSION_FLAG = 'new_version_flag';
export const LEGACY_FLAG = 'legacy_flag';
diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js
index 6fb12cd6270..6f5b03788a8 100644
--- a/app/assets/javascripts/groups/constants.js
+++ b/app/assets/javascripts/groups/constants.js
@@ -12,7 +12,6 @@ export const ACTIVE_TAB_SHARED = 'shared';
export const ACTIVE_TAB_ARCHIVED = 'archived';
export const GROUPS_LIST_HOLDER_CLASS = '.js-groups-list-holder';
-export const GROUPS_FILTER_FORM_CLASS = '.js-group-filter-form';
export const CONTENT_LIST_CLASS = '.groups-list';
export const COMMON_STR = {
diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js
index 65e113e5084..76fbf664913 100644
--- a/app/assets/javascripts/header_search/constants.js
+++ b/app/assets/javascripts/header_search/constants.js
@@ -12,10 +12,6 @@ export const MSG_MR_IVE_CREATED = s__("GlobalSearch|Merge requests I've created"
export const MSG_IN_ALL_GITLAB = s__('GlobalSearch|all GitLab');
-export const MSG_IN_GROUP = s__('GlobalSearch|group');
-
-export const MSG_IN_PROJECT = s__('GlobalSearch|project');
-
export const ICON_PROJECT = 'project';
export const ICON_GROUP = 'group';
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index 0e0734159bf..34b4e436392 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -79,7 +79,6 @@ export const READ_MORE_TEXT = s__(
export const INVITE_BUTTON_TEXT = s__('InviteMembersModal|Invite');
export const INVITE_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Manage members');
export const CANCEL_BUTTON_TEXT = s__('InviteMembersModal|Cancel');
-export const CANCEL_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Explore paid plans');
export const HEADER_CLOSE_LABEL = s__('InviteMembersModal|Close invite team members');
export const MEMBER_ERROR_LIST_TEXT = s__(
'InviteMembersModal|Review the invite errors and try again:',
diff --git a/app/assets/javascripts/issuable/components/status_box.vue b/app/assets/javascripts/issuable/components/status_box.vue
index 0c75e44443d..c27fa166b69 100644
--- a/app/assets/javascripts/issuable/components/status_box.vue
+++ b/app/assets/javascripts/issuable/components/status_box.vue
@@ -4,7 +4,7 @@ import Vue from 'vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import { __ } from '~/locale';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { IssuableStates } from '~/vue_shared/issuable/list/constants';
export const badgeState = Vue.observable({
@@ -76,7 +76,7 @@ export default {
return [
CLASSES[this.state],
{
- 'gl-vertical-align-bottom': this.issuableType === IssuableType.MergeRequest,
+ 'gl-vertical-align-bottom': this.issuableType === TYPE_MERGE_REQUEST,
},
];
},
@@ -84,7 +84,7 @@ export default {
if (this.state === IssuableStates.Opened) {
return 'success';
} else if (this.state === IssuableStates.Closed) {
- return this.issuableType === IssuableType.MergeRequest ? 'danger' : 'info';
+ return this.issuableType === TYPE_MERGE_REQUEST ? 'danger' : 'info';
}
return 'info';
},
diff --git a/app/assets/javascripts/issues/constants.js b/app/assets/javascripts/issues/constants.js
index 52e012c1e6c..7a89ce6554a 100644
--- a/app/assets/javascripts/issues/constants.js
+++ b/app/assets/javascripts/issues/constants.js
@@ -9,6 +9,7 @@ export const TITLE_LENGTH_MAX = 255;
export const TYPE_EPIC = 'epic';
export const TYPE_INCIDENT = 'incident';
export const TYPE_ISSUE = 'issue';
+export const TYPE_MERGE_REQUEST = 'merge_request';
export const WORKSPACE_GROUP = 'group';
export const WORKSPACE_PROJECT = 'project';
diff --git a/app/assets/javascripts/jobs/components/table/constants.js b/app/assets/javascripts/jobs/components/table/constants.js
index 41ce6e4d64d..1b572e60c58 100644
--- a/app/assets/javascripts/jobs/components/table/constants.js
+++ b/app/assets/javascripts/jobs/components/table/constants.js
@@ -1,7 +1,6 @@
import { s__, __ } from '~/locale';
/* Error constants */
-export const POST_FAILURE = 'post_failure';
export const DEFAULT = 'default';
export const RAW_TEXT_WARNING = s__(
'Jobs|Raw text search is not currently supported for the jobs filtered search feature. Please use the available search tokens.',
diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js
index 68c5831db62..8e5b88d362e 100644
--- a/app/assets/javascripts/members/constants.js
+++ b/app/assets/javascripts/members/constants.js
@@ -192,8 +192,6 @@ export const MEMBER_STATE_ACTIVE = 2;
export const BADGE_LABELS_AWAITING_SIGNUP = __('Awaiting user signup');
export const BADGE_LABELS_PENDING = __('Pending owner action');
-export const DAYS_TO_EXPIRE_SOON = 7;
-
export const LEAVE_MODAL_ID = 'member-leave-modal';
export const REMOVE_GROUP_LINK_MODAL_ID = 'remove-group-link-modal-id';
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 1b506c6564b..faef4b01c27 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -226,12 +226,6 @@ export const OVERVIEW_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml';
*/
export const OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX = 'config/prometheus/';
-export const OPERATORS = {
- greaterThan: '>',
- equalTo: '==',
- lessThan: '<',
-};
-
/**
* Dashboard yml files support custom user-defined variables that
* are rendered as input elements in the monitoring dashboard.
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index b8875b5dc18..5f369bea4df 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -27,15 +27,7 @@ export const PACKAGE_TYPE_DEBIAN = 'DEBIAN';
export const PACKAGE_TYPE_HELM = 'HELM';
export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction';
-export const TRACKING_LABEL_CONAN_INSTALLATION = 'conan_installation';
export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation';
-export const TRACKING_LABEL_NPM_INSTALLATION = 'npm_installation';
-export const TRACKING_LABEL_NUGET_INSTALLATION = 'nuget_installation';
-export const TRACKING_LABEL_PYPI_INSTALLATION = 'pypi_installation';
-export const TRACKING_LABEL_COMPOSER_INSTALLATION = 'composer_installation';
-
-export const TRACKING_ACTION_INSTALLATION = 'installation';
-export const TRACKING_ACTION_REGISTRY_SETUP = 'registry_setup';
export const TRACKING_ACTION_COPY_CONAN_COMMAND = 'copy_conan_command';
export const TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND = 'copy_conan_setup_command';
@@ -68,7 +60,6 @@ export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND =
export const TRACKING_LABEL_PACKAGE_ASSET = 'package_assets';
-export const TRACKING_ACTION_DOWNLOAD_PACKAGE_ASSET = 'download_package_asset';
export const TRACKING_ACTION_EXPAND_PACKAGE_ASSET = 'expand_package_asset';
export const TRACKING_ACTION_COPY_PACKAGE_ASSET_SHA = 'copy_package_asset_sha';
@@ -147,8 +138,6 @@ export const PACKAGE_REGISTRY_TITLE = __('Package Registry');
export const PACKAGE_ERROR_STATUS = 'ERROR';
export const PACKAGE_DEFAULT_STATUS = 'DEFAULT';
-export const PACKAGE_HIDDEN_STATUS = 'HIDDEN';
-export const PACKAGE_PROCESSING_STATUS = 'PROCESSING';
export const NPM_PACKAGE_MANAGER = 'npm';
export const YARN_PACKAGE_MANAGER = 'yarn';
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/constants.js b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
index c93cd7f7d78..b47759df35f 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
@@ -78,8 +78,4 @@ export const MAVEN_FORWARDING_FIELDS = {
// Parameters
-export const PACKAGES_DOCS_PATH = helpPagePath('user/packages/index');
-export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed';
-export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex';
-
export const DEPENDENCY_PROXY_DOCS_PATH = helpPagePath('user/packages/dependency_proxy/index');
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 820501089ed..ca146ac1e87 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -1,7 +1,6 @@
import { s__, __ } from '~/locale';
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
-export const LAYOUT_CHANGE_DELAY = 300;
export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status', 'source'];
@@ -35,8 +34,6 @@ export const RAW_TEXT_WARNING = s__(
export const DEFAULT = 'default';
export const DELETE_FAILURE = 'delete_pipeline_failure';
export const DRAW_FAILURE = 'draw_failure';
-export const EMPTY_PIPELINE_DATA = 'empty_data';
-export const INVALID_CI_CONFIG = 'invalid_ci_config';
export const LOAD_FAILURE = 'load_failure';
export const PARSE_FAILURE = 'parse_failure';
export const POST_FAILURE = 'post_failure';
diff --git a/app/assets/javascripts/protected_branches/constants.js b/app/assets/javascripts/protected_branches/constants.js
index ae5eaa8e622..b5d00cb7e82 100644
--- a/app/assets/javascripts/protected_branches/constants.js
+++ b/app/assets/javascripts/protected_branches/constants.js
@@ -9,12 +9,3 @@ export const LEVEL_TYPES = {
GROUP: 'group',
DEPLOY_KEY: 'deploy_key',
};
-
-export const LEVEL_ID_PROP = {
- ROLE: 'access_level',
- USER: 'user_id',
- GROUP: 'group_id',
- DEPLOY_KEY: 'deploy_key_id',
-};
-
-export const ACCESS_LEVEL_NONE = 0;
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index 7b0ade09dff..6beb6cd4d34 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -65,7 +65,6 @@ export const DAST_PROFILES_NAME = __('DAST profiles');
export const DAST_PROFILES_DESCRIPTION = s__(
'SecurityConfiguration|Manage profiles for use by DAST scans.',
);
-export const DAST_PROFILES_HELP_PATH = helpPagePath('user/application_security/dast/index');
export const DAST_PROFILES_CONFIG_TEXT = s__('SecurityConfiguration|Manage profiles');
export const SECRET_DETECTION_NAME = __('Secret Detection');
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
index 323f6f23df6..d65c950b33a 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
@@ -1,6 +1,6 @@
<script>
import { GlIcon } from '@gitlab/ui';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { __, sprintf } from '~/locale';
export default {
@@ -32,7 +32,7 @@ export default {
);
},
isMergeRequest() {
- return this.issuableType === IssuableType.MergeRequest;
+ return this.issuableType === TYPE_MERGE_REQUEST;
},
hasMergeIcon() {
const canMerge = this.user.mergeRequestInteraction?.canMerge || this.user.can_merge;
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
index 73cd0044c16..2c6eb0e5001 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
@@ -1,6 +1,6 @@
<script>
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import { isUserBusy } from '~/set_status_modal/utils';
@@ -73,7 +73,7 @@ export default {
},
computed: {
isMergeRequest() {
- return this.issuableType === IssuableType.MergeRequest;
+ return this.issuableType === TYPE_MERGE_REQUEST;
},
cannotMerge() {
const canMerge = this.user.mergeRequestInteraction?.canMerge || this.user.can_merge;
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
index 8893e90b1e5..ef27dbd4692 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
@@ -2,7 +2,7 @@
import { GlDropdownItem } from '@gitlab/ui';
import Vue from 'vue';
import { createAlert } from '~/flash';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { IssuableType, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { __, n__ } from '~/locale';
import UserSelect from '~/vue_shared/components/user_select/user_select.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -60,7 +60,7 @@ export default {
required: false,
default: TYPE_ISSUE,
validator(value) {
- return [TYPE_ISSUE, IssuableType.MergeRequest, IssuableType.Alert].includes(value);
+ return [TYPE_ISSUE, TYPE_MERGE_REQUEST, IssuableType.Alert].includes(value);
},
},
issuableId: {
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue
index ddbd8866680..8b40b48b54a 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue
@@ -1,6 +1,6 @@
<script>
import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { s__, sprintf } from '~/locale';
const AVAILABILITY_STATUS = {
@@ -39,7 +39,7 @@ export default {
);
},
hasCannotMergeIcon() {
- return this.issuableType === IssuableType.MergeRequest && !this.user.canMerge;
+ return this.issuableType === TYPE_MERGE_REQUEST && !this.user.canMerge;
},
},
};
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
index 71f349bb87e..b424d9074d0 100644
--- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -1,6 +1,6 @@
<script>
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { __, sprintf } from '~/locale';
import AssigneeAvatarLink from './assignee_avatar_link.vue';
import UserNameWithStatus from './user_name_with_status.vue';
@@ -53,7 +53,7 @@ export default {
return `@${this.firstUser.username}`;
},
isMergeRequest() {
- return this.issuableType === IssuableType.MergeRequest;
+ return this.issuableType === TYPE_MERGE_REQUEST;
},
},
methods: {
@@ -61,7 +61,7 @@ export default {
this.showLess = !this.showLess;
},
userAvailability(u) {
- if (this.issuableType === IssuableType.MergeRequest) {
+ if (this.issuableType === TYPE_MERGE_REQUEST) {
return u?.availability || '';
}
return u?.status?.availability || '';
diff --git a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue
index bf916e26a15..7d34cdcd908 100644
--- a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue
+++ b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue
@@ -4,7 +4,7 @@ import issuableLabelsSubscription from 'ee_else_ce/sidebar/queries/issuable_labe
import { MutationOperationMode, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { createAlert } from '~/flash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { __ } from '~/locale';
import { issuableLabelsQueries } from '../../../constants';
@@ -264,7 +264,7 @@ export default {
case TYPE_ISSUE:
case IssuableType.TestCase:
return updateVariables;
- case IssuableType.MergeRequest:
+ case TYPE_MERGE_REQUEST:
return {
...updateVariables,
operationMode: MutationOperationMode.Replace,
@@ -324,7 +324,7 @@ export default {
...removeVariables,
removeLabelIds: [labelId],
};
- case IssuableType.MergeRequest:
+ case TYPE_MERGE_REQUEST:
return {
...removeVariables,
labelIds: [labelId],
diff --git a/app/assets/javascripts/sidebar/components/milestone/milestone_dropdown.vue b/app/assets/javascripts/sidebar/components/milestone/milestone_dropdown.vue
index 69ed881a1e6..24afb25e403 100644
--- a/app/assets/javascripts/sidebar/components/milestone/milestone_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/milestone/milestone_dropdown.vue
@@ -2,7 +2,12 @@
import { GlDropdownItem } from '@gitlab/ui';
import { TYPENAME_MILESTONE } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { IssuableType, TYPE_ISSUE, WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
+import {
+ TYPE_ISSUE,
+ TYPE_MERGE_REQUEST,
+ WORKSPACE_GROUP,
+ WORKSPACE_PROJECT,
+} from '~/issues/constants';
import { __ } from '~/locale';
import { IssuableAttributeType } from '../../constants';
import SidebarDropdown from '../sidebar_dropdown.vue';
@@ -37,7 +42,7 @@ export default {
type: String,
required: true,
validator(value) {
- return [TYPE_ISSUE, IssuableType.MergeRequest].includes(value);
+ return [TYPE_ISSUE, TYPE_MERGE_REQUEST].includes(value);
},
},
inputName: {
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
index 6c0038aae1f..7068ba98966 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
@@ -9,9 +9,9 @@ import {
} from '@gitlab/ui';
import { kebabCase, snakeCase } from 'lodash';
import {
- IssuableType,
TYPE_EPIC,
TYPE_ISSUE,
+ TYPE_MERGE_REQUEST,
WORKSPACE_GROUP,
WORKSPACE_PROJECT,
} from '~/issues/constants';
@@ -76,7 +76,7 @@ export default {
type: String,
required: true,
validator(value) {
- return [TYPE_ISSUE, IssuableType.MergeRequest].includes(value);
+ return [TYPE_ISSUE, TYPE_MERGE_REQUEST].includes(value);
},
},
workspaceType: {
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index 5df65c4aaaf..660e7b98155 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -3,7 +3,7 @@ import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab
import { kebabCase, snakeCase } from 'lodash';
import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { timeFor } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -71,7 +71,7 @@ export default {
type: String,
required: true,
validator(value) {
- return [TYPE_ISSUE, IssuableType.MergeRequest].includes(value);
+ return [TYPE_ISSUE, TYPE_MERGE_REQUEST].includes(value);
},
},
icon: {
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
index cbe839d1112..226785d859c 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
@@ -1,7 +1,7 @@
<script>
import { GlDropdownForm, GlIcon, GlLoadingIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
import { createAlert } from '~/flash';
-import { IssuableType, TYPE_EPIC } from '~/issues/constants';
+import { TYPE_EPIC, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __, sprintf } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -87,7 +87,7 @@ export default {
},
computed: {
isMergeRequest() {
- return this.issuableType === IssuableType.MergeRequest && this.glFeatures.movedMrSidebar;
+ return this.issuableType === TYPE_MERGE_REQUEST && this.glFeatures.movedMrSidebar;
},
isLoading() {
return this.$apollo.queries?.subscribed?.loading || this.loading;
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index c645b1649d2..f6968558122 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -8,7 +8,7 @@ import {
GlLoadingIcon,
GlTooltipDirective,
} from '@gitlab/ui';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__, __ } from '~/locale';
@@ -173,7 +173,7 @@ export default {
return Boolean(this.showHelp);
},
isTimeReportSupported() {
- return [TYPE_ISSUE, IssuableType.MergeRequest].includes(this.issuableType) && this.issuableId;
+ return [TYPE_ISSUE, TYPE_MERGE_REQUEST].includes(this.issuableType) && this.issuableId;
},
timeTrackingIconTitle() {
return this.showHelpState ? '' : HOW_TO_TRACK_TIME;
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index 499e58de19e..43f29157805 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -7,6 +7,7 @@ import {
IssuableType,
TYPE_EPIC,
TYPE_ISSUE,
+ TYPE_MERGE_REQUEST,
WORKSPACE_GROUP,
WORKSPACE_PROJECT,
} from '~/issues/constants';
@@ -75,7 +76,7 @@ export const assigneesQueries = {
subscription: issuableAssigneesSubscription,
mutation: updateIssueAssigneesMutation,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: getMergeRequestAssignees,
mutation: updateMergeRequestAssigneesMutation,
},
@@ -89,7 +90,7 @@ export const participantsQueries = {
[TYPE_ISSUE]: {
query: issueParticipantsQuery,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: getMergeRequestParticipants,
},
[TYPE_EPIC]: {
@@ -105,7 +106,7 @@ export const userSearchQueries = {
[TYPE_ISSUE]: {
query: userSearchQuery,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: userSearchWithMRPermissionsQuery,
},
};
@@ -125,7 +126,7 @@ export const referenceQueries = {
[TYPE_ISSUE]: {
query: issueReferenceQuery,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: mergeRequestReferenceQuery,
},
[TYPE_EPIC]: {
@@ -148,7 +149,7 @@ export const issuableLabelsQueries = {
mutation: updateIssueLabelsMutation,
mutationName: 'updateIssue',
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
issuableQuery: mergeRequestLabelsQuery,
mutation: updateMergeRequestLabelsMutation,
mutationName: 'mergeRequestSetLabels',
@@ -192,7 +193,7 @@ export const subscribedQueries = {
query: epicSubscribedQuery,
mutation: updateEpicSubscriptionMutation,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: mergeRequestSubscribed,
mutation: updateMergeRequestSubscriptionMutation,
},
@@ -207,7 +208,7 @@ export const timeTrackingQueries = {
[TYPE_ISSUE]: {
query: issueTimeTrackingQuery,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: mergeRequestTimeTrackingQuery,
},
};
@@ -234,7 +235,7 @@ export const timelogQueries = {
[TYPE_ISSUE]: {
query: getIssueTimelogsQuery,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: getMrTimelogsQuery,
},
};
@@ -246,7 +247,7 @@ export const issuableMilestoneQueries = {
query: projectIssueMilestoneQuery,
mutation: projectIssueMilestoneMutation,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: mergeRequestMilestone,
mutation: mergeRequestMilestoneMutation,
},
@@ -259,7 +260,7 @@ export const milestonesQueries = {
[WORKSPACE_PROJECT]: projectMilestonesQuery,
},
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: {
[WORKSPACE_GROUP]: groupMilestonesQuery,
[WORKSPACE_PROJECT]: projectMilestonesQuery,
@@ -295,7 +296,7 @@ export const todoQueries = {
[TYPE_ISSUE]: {
query: issueTodoQuery,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: mergeRequestTodoQuery,
},
};
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index fb024d818da..510b2fb9af9 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -4,7 +4,7 @@ import { TYPENAME_ISSUE, TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constan
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { gqlClient } from '~/issues/list/graphql';
import {
isInDesignPage,
@@ -81,7 +81,7 @@ function mountSidebarTodoWidget() {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
},
}),
});
@@ -125,7 +125,7 @@ function mountSidebarAssigneesDeprecated(mediator) {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
issuableId: id,
assigneeAvailabilityStatus,
},
@@ -142,7 +142,7 @@ function mountSidebarAssigneesWidget() {
const { id, iid, fullPath, editable } = getSidebarOptions();
const isIssuablePage = isInIssuePage() || isInIncidentPage() || isInDesignPage();
- const issuableType = isIssuablePage ? TYPE_ISSUE : IssuableType.MergeRequest;
+ const issuableType = isIssuablePage ? TYPE_ISSUE : TYPE_MERGE_REQUEST;
// eslint-disable-next-line no-new
new Vue({
el,
@@ -204,8 +204,7 @@ function mountSidebarReviewers(mediator) {
issuableIid: String(iid),
projectPath: fullPath,
field: el.dataset.field,
- issuableType:
- isInIssuePage() || isInDesignPage() ? TYPE_ISSUE : IssuableType.MergeRequest,
+ issuableType: isInIssuePage() || isInDesignPage() ? TYPE_ISSUE : TYPE_MERGE_REQUEST,
},
}),
});
@@ -275,8 +274,7 @@ function mountSidebarMilestoneWidget() {
attrWorkspacePath: projectPath,
workspacePath: projectPath,
iid: issueIid,
- issuableType:
- isInIssuePage() || isInDesignPage() ? TYPE_ISSUE : IssuableType.MergeRequest,
+ issuableType: isInIssuePage() || isInDesignPage() ? TYPE_ISSUE : TYPE_MERGE_REQUEST,
issuableAttribute: IssuableAttributeType.Milestone,
icon: 'clock',
},
@@ -313,7 +311,7 @@ export function mountMilestoneDropdown() {
attrWorkspacePath: fullPath,
canAdminMilestone,
inputName,
- issuableType: isInIssuePage() ? TYPE_ISSUE : IssuableType.MergeRequest,
+ issuableType: isInIssuePage() ? TYPE_ISSUE : TYPE_MERGE_REQUEST,
milestoneId,
milestoneTitle,
projectMilestonesPath,
@@ -358,7 +356,7 @@ export function mountSidebarLabelsWidget() {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
workspaceType: 'project',
attrWorkspacePath: el.dataset.projectPath,
labelCreateType: LabelType.project,
@@ -398,7 +396,7 @@ function mountSidebarConfidentialityWidget() {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
},
}),
});
@@ -454,7 +452,7 @@ function mountSidebarReferenceWidget() {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
},
}),
});
@@ -506,7 +504,7 @@ function mountSidebarParticipantsWidget() {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
},
}),
});
@@ -536,7 +534,7 @@ function mountSidebarSubscriptionsWidget() {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? TYPE_ISSUE
- : IssuableType.MergeRequest,
+ : TYPE_MERGE_REQUEST,
},
}),
});
diff --git a/app/assets/javascripts/usage_quotas/storage/constants.js b/app/assets/javascripts/usage_quotas/storage/constants.js
index fab18cefc60..bd8cd372ecf 100644
--- a/app/assets/javascripts/usage_quotas/storage/constants.js
+++ b/app/assets/javascripts/usage_quotas/storage/constants.js
@@ -26,7 +26,6 @@ export const uploadsPopoverContent = s__(
'NamespaceStorage|Uploads are not counted in namespace storage quotas.',
);
-export const PROJECT_TABLE_LABEL_PROJECT = __('Project');
export const PROJECT_TABLE_LABEL_STORAGE_TYPE = s__('UsageQuota|Storage type');
export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js
index 85ae298fcea..dbfbd35b9b6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/constants.js
@@ -163,9 +163,6 @@ export const EXTENSION_ICON_CLASS = {
severityUnknown: 'gl-text-gray-400',
};
-export const EXTENSION_SUMMARY_FAILED_CLASS = 'gl-text-red-500';
-export const EXTENSION_SUMMARY_NEUTRAL_CLASS = 'gl-text-gray-700';
-
export const TELEMETRY_WIDGET_VIEWED = 'WIDGET_VIEWED';
export const TELEMETRY_WIDGET_EXPANDED = 'WIDGET_EXPANDED';
export const TELEMETRY_WIDGET_FULL_REPORT_CLICKED = 'WIDGET_FULL_REPORT_CLICKED';
diff --git a/app/assets/javascripts/vue_shared/components/pagination/constants.js b/app/assets/javascripts/vue_shared/components/pagination/constants.js
index 748ad178c70..f8a6d37dea1 100644
--- a/app/assets/javascripts/vue_shared/components/pagination/constants.js
+++ b/app/assets/javascripts/vue_shared/components/pagination/constants.js
@@ -1,8 +1,5 @@
import { s__ } from '~/locale';
-export const PAGINATION_UI_BUTTON_LIMIT = 4;
-export const UI_LIMIT = 6;
-export const SPREAD = '...';
export const PREV = s__('Pagination|Prev');
export const NEXT = s__('Pagination|Next');
export const FIRST = s__('Pagination|« First');
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
index 15335ea6edc..514b626ed95 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
@@ -141,8 +141,6 @@ export const BIDI_CHARS_CLASS_LIST = 'unicode-bidi has-tooltip';
export const BIDI_CHAR_TOOLTIP = 'Potentially unwanted character detected: Unicode BiDi Control';
-export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight';
-
// We fallback to highlighting these languages with Rouge, see the following issue for more detail:
// https://gitlab.com/gitlab-org/gitlab/-/issues/384375#note_1212752013
export const LEGACY_FALLBACKS = ['python'];
diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
index edcfabe7da3..abd3575d020 100644
--- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
+++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
@@ -11,7 +11,7 @@ import {
} from '@gitlab/ui';
import { __ } from '~/locale';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { participantsQueries, userSearchQueries } from '~/sidebar/constants';
import { TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constants';
@@ -149,7 +149,7 @@ export default {
},
computed: {
isMergeRequest() {
- return this.issuableType === IssuableType.MergeRequest;
+ return this.issuableType === TYPE_MERGE_REQUEST;
},
searchUsersVariables() {
const variables = {
diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js
index 1463f37171c..29a31503840 100644
--- a/app/assets/javascripts/vue_shared/constants.js
+++ b/app/assets/javascripts/vue_shared/constants.js
@@ -75,8 +75,6 @@ export const timeRanges = [
/* eslint-enable @gitlab/require-i18n-strings */
export const defaultTimeRange = timeRanges.find((tr) => tr.default);
-export const getTimeWindow = (timeWindowName) =>
- timeRanges.find((tr) => tr.name === timeWindowName);
export const AVATAR_SHAPE_OPTION_CIRCLE = 'circle';
export const AVATAR_SHAPE_OPTION_RECT = 'rect';
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 66b14657134..0bbfeae6656 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -13,6 +13,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
before_action :disable_query_limiting, only: [:usage_data]
+ before_action do
+ push_frontend_feature_flag(:ci_variables_pages, current_user)
+ end
+
feature_category :not_owned, [ # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
:general, :reporting, :metrics_and_profiling, :network,
:preferences, :update, :reset_health_check_token
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index 78e3ffa4af9..4bbaf92b126 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -12,6 +12,11 @@ module Groups
before_action :assign_variables_to_gon, only: [:show]
feature_category :continuous_integration
+
+ before_action do
+ push_frontend_feature_flag(:ci_variables_pages, current_user)
+ end
+
urgency :low
def show
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index b330aacf3e9..f5588a35ad5 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -12,6 +12,10 @@ module Projects
before_action :check_builds_available!
before_action :define_variables
+ before_action do
+ push_frontend_feature_flag(:ci_variables_pages, current_user)
+ end
+
helper_method :highlight_badge
feature_category :continuous_integration
diff --git a/app/models/clusters/applications/crossplane.rb b/app/models/clusters/applications/crossplane.rb
deleted file mode 100644
index a7b4fb57149..00000000000
--- a/app/models/clusters/applications/crossplane.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Applications
- # DEPRECATED for removal in %14.0
- # See https://gitlab.com/groups/gitlab-org/-/epics/4280
- class Crossplane < ApplicationRecord
- VERSION = '0.4.1'
-
- self.table_name = 'clusters_applications_crossplane'
-
- include ::Clusters::Concerns::ApplicationCore
- include ::Clusters::Concerns::ApplicationStatus
- include ::Clusters::Concerns::ApplicationVersion
- include ::Clusters::Concerns::ApplicationData
-
- attribute :version, default: VERSION
- attribute :stack, default: ""
-
- validates :stack, presence: true
-
- def chart
- 'crossplane/crossplane'
- end
-
- def repository
- 'https://charts.crossplane.io/alpha'
- end
-
- def install_command
- helm_command_module::InstallCommand.new(
- name: 'crossplane',
- repository: repository,
- version: VERSION,
- rbac: cluster.platform_kubernetes_rbac?,
- chart: chart,
- files: files
- )
- end
-
- def values
- crossplane_values.to_yaml
- end
-
- private
-
- def crossplane_values
- {
- "clusterStacks" => {
- self.stack => {
- "deploy" => true
- }
- }
- }
- end
- end
- end
-end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index a35ea6ddb46..071759318c7 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -14,7 +14,6 @@ module Clusters
APPLICATIONS = {
Clusters::Applications::Helm.application_name => Clusters::Applications::Helm,
Clusters::Applications::Ingress.application_name => Clusters::Applications::Ingress,
- Clusters::Applications::Crossplane.application_name => Clusters::Applications::Crossplane,
Clusters::Applications::Prometheus.application_name => Clusters::Applications::Prometheus,
Clusters::Applications::Runner.application_name => Clusters::Applications::Runner,
Clusters::Applications::Jupyter.application_name => Clusters::Applications::Jupyter,
@@ -56,7 +55,6 @@ module Clusters
has_one_cluster_application :helm
has_one_cluster_application :ingress
- has_one_cluster_application :crossplane
has_one_cluster_application :prometheus
has_one_cluster_application :runner
has_one_cluster_application :jupyter
diff --git a/config/feature_flags/development/ci_use_downstream_pipeline_duration_for_calculation.yml b/config/feature_flags/development/ci_variables_pages.yml
index b4cc97c181e..49fc081ede4 100644
--- a/config/feature_flags/development/ci_use_downstream_pipeline_duration_for_calculation.yml
+++ b/config/feature_flags/development/ci_variables_pages.yml
@@ -1,8 +1,8 @@
----
-name: ci_use_downstream_pipeline_duration_for_calculation
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109445
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388776
-milestone: '15.9'
+name: ci_variables_pages
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110817
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/392874
+milestone: '15.10'
type: development
-group: group::pipeline execution
+group: group::pipeline security
default_enabled: false
+
diff --git a/db/docs/clusters_applications_crossplane.yml b/db/docs/clusters_applications_crossplane.yml
index f633f746e70..c440eb2487b 100644
--- a/db/docs/clusters_applications_crossplane.yml
+++ b/db/docs/clusters_applications_crossplane.yml
@@ -1,7 +1,5 @@
---
table_name: clusters_applications_crossplane
-classes:
-- Clusters::Applications::Crossplane
feature_categories:
- kubernetes_management
description: "(Deprecated) A GitLab managed Crossplane installation in a Kubernetes cluster"
diff --git a/doc/development/fe_guide/widgets.md b/doc/development/fe_guide/widgets.md
index edb8559da48..6cd8e6c091c 100644
--- a/doc/development/fe_guide/widgets.md
+++ b/doc/development/fe_guide/widgets.md
@@ -62,11 +62,11 @@ Because we need different GraphQL queries and mutations for different sidebars,
```javascript
export const assigneesQueries = {
- [IssuableType.Issue]: {
+ [TYPE_ISSUE]: {
query: getIssueParticipants,
mutation: updateAssigneesMutation,
},
- [IssuableType.MergeRequest]: {
+ [TYPE_MERGE_REQUEST]: {
query: getMergeRequestParticipants,
mutation: updateMergeRequestParticipantsMutation,
},
diff --git a/doc/development/fips_compliance.md b/doc/development/fips_compliance.md
index 1f9abef1f44..04500a7534a 100644
--- a/doc/development/fips_compliance.md
+++ b/doc/development/fips_compliance.md
@@ -274,104 +274,55 @@ all:
gitlab_charts_custom_config_file: '/path/to/gitlab-environment-toolkit/ansible/environments/gitlab-10k/inventory/charts.yml'
```
-Now create `charts.yml` in the location specified above and specify tags with a `-fips` suffix. For example:
+Now create `charts.yml` in the location specified above and specify tags with a `-fips` suffix.
-```yaml
-global:
- image:
- pullPolicy: Always
- certificates:
- image:
- tag: master-fips
- kubectl:
- image:
- tag: master-fips
-
-gitlab:
- gitaly:
- image:
- tag: master-fips
- gitlab-exporter:
- image:
- tag: master-fips
- gitlab-shell:
- image:
- tag: main-fips # The default branch is main, not master
- gitlab-mailroom:
- image:
- tag: master-fips
- gitlab-pages:
- image:
- tag: master-fips
- migrations:
- image:
- tag: master-fips
- sidekiq:
- image:
- tag: master-fips
- toolbox:
- image:
- tag: master-fips
- webservice:
- image:
- tag: master-fips
- workhorse:
- tag: master-fips
-
-nginx-ingress:
- controller:
- image:
- repository: registry.gitlab.com/gitlab-org/cloud-native/charts/gitlab-ingress-nginx/controller
- tag: v1.2.1-fips
- pullPolicy: Always
- digest: sha256:c4222b7ab3836b9be2a7649cff4b2e6ead34286dfdf3a7b04eb34fdd3abb0334
-```
-
-The above example shows a FIPS-enabled [`nginx-ingress`](https://github.com/kubernetes/ingress-nginx) image.
-See our [Charts documentation on FIPS](https://docs.gitlab.com/charts/advanced/fips/index.html) for more details.
+See our [Charts documentation on FIPS](https://docs.gitlab.com/charts/advanced/fips/index.html) for more details, including
+an [example values file](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/examples/fips/values.yaml) as a reference.
You can also use release tags, but the versioning is tricky because each
component may use its own versioning scheme. For example, for GitLab v15.2:
```yaml
global:
+ image:
+ tagSuffix: -fips
certificates:
image:
- tag: 20211220-r0-fips
+ tag: 20211220-r0
kubectl:
image:
- tag: 1.18.20-fips
+ tag: 1.18.20
gitlab:
gitaly:
image:
- tag: v15.2.0-fips
+ tag: v15.2.0
gitlab-exporter:
image:
- tag: 11.17.1-fips
+ tag: 11.17.1
gitlab-shell:
image:
- tag: v14.9.0-fips
+ tag: v14.9.0
gitlab-mailroom:
image:
- tag: v15.2.0-fips
+ tag: v15.2.0
gitlab-pages:
image:
- tag: v1.61.0-fips
+ tag: v1.61.0
migrations:
image:
- tag: v15.2.0-fips
+ tag: v15.2.0
sidekiq:
image:
- tag: v15.2.0-fips
+ tag: v15.2.0
toolbox:
image:
- tag: v15.2.0-fips
+ tag: v15.2.0
webservice:
image:
- tag: v15.2.0-fips
+ tag: v15.2.0
workhorse:
- tag: v15.2.0-fips
+ tag: v15.2.0
```
## FIPS Performance Benchmarking
diff --git a/doc/integration/jira/configure.md b/doc/integration/jira/configure.md
index dc00deec7a6..3ef4dfac3f4 100644
--- a/doc/integration/jira/configure.md
+++ b/doc/integration/jira/configure.md
@@ -13,10 +13,10 @@ and for self-managed GitLab, at an [instance level](../../user/admin_area/settin
Prerequisites:
-- Ensure your GitLab installation does not use a [relative URL](development_panel.md#limitations).
+- Ensure your GitLab installation does not use a [relative URL](https://docs.gitlab.com/omnibus/settings/configuration.html#configure-a-relative-url-for-gitlab).
- For **Jira Server**, ensure you have a Jira username and password.
See [authentication in Jira](index.md#authentication-in-jira).
-- For **Jira on Atlassian cloud**, ensure you have an API token
+- For **Jira Cloud**, ensure you have an API token
and the email address you used to create the token.
See [authentication in Jira](index.md#authentication-in-jira).
diff --git a/doc/integration/jira/development_panel.md b/doc/integration/jira/development_panel.md
index 49f2664893d..e358c66eeee 100644
--- a/doc/integration/jira/development_panel.md
+++ b/doc/integration/jira/development_panel.md
@@ -10,22 +10,24 @@ info: To determine the technical writer assigned to the Stage/Group associated w
You can view GitLab activity from the Jira development panel.
-When you are in GitLab, you refer to a Jira issue by ID. Then
-[the activity](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/)
-for that issue is displayed in the Jira development panel.
+When you're in GitLab, you can refer to a Jira issue by ID.
+The [activity for that issue](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/)
+is displayed in the Jira development panel.
In the Jira development panel, you can create a GitLab merge request from a branch.
-You can also create a GitLab branch from a Jira Cloud issue
+You can also create a GitLab branch from a Jira issue in the GitLab for Jira Cloud app
([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66032) in GitLab 14.2).
## Connected projects in GitLab
-The Jira development panel connects to the Jira instance all GitLab projects in:
+The Jira development panel connects to the entire Jira instance all GitLab projects in:
- A top-level group, including all projects in its subgroups.
- A personal namespace.
-## Where the Jira ID displayed
+These GitLab projects can interact with all Jira projects in that instance.
+
+## Information displayed in the panel
The information displayed in the Jira development panel depends on where you mention the Jira issue ID in GitLab.
@@ -44,48 +46,52 @@ Jira Smart Commits are special commands to process a Jira issue. With these comm
- Log time against a Jira issue.
- Transition a Jira issue to any status defined in the project workflow.
-For more information, see [Using Smart Commits](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html)
-in the Atlassian documentation.
+For more information, see the
+[Atlassian documentation](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html).
-## Configure the integration
+## Configure the panel
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
-For an overview of how to configure the Jira development panel integration, see
-[Agile Management - GitLab Jira development panel integration](https://www.youtube.com/watch?v=VjVTOmMl85M).
+For an overview, see [Jira development panel integration](https://www.youtube.com/watch?v=VjVTOmMl85M).
+
+### For GitLab.com
-To simplify administration, we recommend that a GitLab group maintainer or group owner
-(or, if possible, instance administrator in the case of self-managed GitLab) set up the integration.
+Prerequisite:
-| Jira usage | GitLab.com customers need | GitLab self-managed customers need |
-|------------|---------------------------|------------------------------------|
-| [Atlassian cloud](https://www.atlassian.com/migration/assess/why-cloud) | The [GitLab for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) from the [Atlassian Marketplace](https://marketplace.atlassian.com). This method offers real-time sync between GitLab.com and Jira. The method requires inbound connections for the setup and then pushes data to Jira through outbound connections. For more information, see [GitLab for Jira Cloud app](connect-app.md). | The GitLab for Jira Cloud app [installed manually](connect-app.md#install-the-gitlab-for-jira-cloud-app-manually). By default, you can install the app from the [Atlassian Marketplace](https://marketplace.atlassian.com/). The method requires inbound connections for the setup and then pushes data to Jira through outbound connections. For more information, see [Connect the GitLab for Jira Cloud app for self-managed instances](connect-app.md#connect-the-gitlab-for-jira-cloud-app-for-self-managed-instances). |
-| Your own server | The [Jira DVCS connector](dvcs/index.md). This method syncs data every hour and works only with inbound connections. The method tries to set up webhooks in GitLab to implement real-time data sync, which does not work without outbound connections. | The [Jira DVCS connector](dvcs/index.md). This method syncs data every hour and works only with inbound connections. The method tries to set up webhooks in GitLab to implement real-time data sync, which does not work without outbound connections. |
+- You must have at least the Maintainer role for the group.
-Each GitLab project can be configured to connect to an entire Jira instance. That means after
-configuration, one GitLab project can interact with all Jira projects in that instance. For:
+To configure the Jira development panel on GitLab.com:
-- The [view Jira issues](issues.md#view-jira-issues) feature, you must associate a GitLab project with a
- specific Jira project.
-- Other features, you do not have to explicitly associate a GitLab project with any single Jira
- project.
+- **For [Jira Cloud](https://www.atlassian.com/migration/assess/why-cloud)**:
+ - [From the Atlassian Marketplace, install the GitLab for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-for-jira-cloud?hosting=cloud&tab=overview).
+ - This method syncs data between GitLab.com and Jira in real time.
+ - This method requires inbound connections for the setup and outbound connections to push data to Jira.
+ - For more information, see [GitLab for Jira Cloud app](connect-app.md).
+- **For Jira Server**:
+ - Use the [Jira DVCS connector](dvcs/index.md).
+ - This method syncs data every hour and works only with inbound connections.
+ - This method attempts to set up webhooks in GitLab to sync data in real time, which requires outbound connections.
-If you have a single Jira instance, you can pre-fill the settings. For more information, read the
-documentation for [central administration of project integrations](../../user/admin_area/settings/project_integration_management.md).
+### For self-managed GitLab
-## Limitations
+Prerequisites:
-- This integration is not supported on GitLab instances under a
-[relative URL](https://docs.gitlab.com/omnibus/settings/configuration.html#configure-a-relative-url-for-gitlab)
-(for example, `http://example.com/gitlab`).
-- [Creating a branch](https://gitlab.com/gitlab-org/gitlab/-/issues/2647) is only supported by the GitLab for Jira app and is not available within the DVCS integration. See [officially supported DVCS features](https://confluence.atlassian.com/adminjiraserver/integrating-with-development-tools-938846890.html) for more information.
+- You must have administrator access for the instance.
+- Your GitLab installation must not use a [relative URL](https://docs.gitlab.com/omnibus/settings/configuration.html#configure-a-relative-url-for-gitlab)
+ (for example, `https://example.com/gitlab`).
-## Troubleshoot the development panel
+To configure the Jira development panel on self-managed GitLab:
-If you use Jira on your own server, go to the [Atlassian documentation](https://confluence.atlassian.com/jirakb/troubleshoot-the-development-panel-in-jira-server-574685212.html)
-for general troubleshooting information.
+- **For [Jira Cloud](https://www.atlassian.com/migration/assess/why-cloud)**:
+ - [Install the GitLab for Jira Cloud app manually](connect-app.md#install-the-gitlab-for-jira-cloud-app-manually).
+ - This method requires inbound connections for the setup and outbound connection to push data to Jira.
+ - For more information, see [Connect the GitLab for Jira Cloud app for self-managed instances](connect-app.md#connect-the-gitlab-for-jira-cloud-app-for-self-managed-instances).
+- **For Jira Server**:
+ - Use the [Jira DVCS connector](dvcs/index.md).
+ - This method syncs data every hour and works only with inbound connections.
+ - This method attempts to set up webhooks in GitLab to sync data in real time, which requires outbound connections.
-### Cookies for Oracle's Access Manager
+## Troubleshooting
-To support Oracle's Access Manager, GitLab sends additional cookies
-to enable Basic Auth. The cookie being added to each request is `OBBasicAuth` with
-a value of `fromDialog`.
+To troubleshoot the Jira development panel on your own server, see the
+[Atlassian documentation](https://confluence.atlassian.com/jirakb/troubleshoot-the-development-panel-in-jira-server-574685212.html).
diff --git a/doc/integration/jira/index.md b/doc/integration/jira/index.md
index 5e83a69997e..3ec4c7aee87 100644
--- a/doc/integration/jira/index.md
+++ b/doc/integration/jira/index.md
@@ -37,7 +37,7 @@ displays in the [development panel](https://support.atlassian.com/jira-software-
To set up the Jira development panel integration, use the GitLab for Jira Cloud app
or the Jira DVCS (distributed version control system) connector,
-[depending on your installation](development_panel.md#configure-the-integration).
+[depending on your installation](development_panel.md#configure-the-panel).
### Direct feature comparison
diff --git a/doc/topics/awesome_co.md b/doc/topics/awesome_co.md
index ff3c81a1b90..ffda564cd91 100644
--- a/doc/topics/awesome_co.md
+++ b/doc/topics/awesome_co.md
@@ -141,3 +141,73 @@ create(:project, name: 'Throws Error', namespace: create(:group, name: 'Some Gro
create(:project, name: 'No longer throws error', owner: @owner, namespace: create(:group, name: 'Some Group'))
create(:epic, group: create(:group), author: @owner)
```
+
+## YAML Factories
+
+### Generator to generate _n_ amount of records
+
+### [Group Labels](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/factories/labels.rb)
+
+```yaml
+group_labels:
+ # Group Label with Name and a Color
+ - name: Group Label 1
+ group_id: <%= @group.id %>
+ color: "#FF0000"
+```
+
+### [Group Milestones](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/factories/milestones.rb)
+
+```yaml
+group_milestones:
+ # Past Milestone
+ - name: Past Milestone
+ group_id: <%= @group.id %>
+ group:
+ start_date: <%= 1.month.ago %>
+ due_date: <%= 1.day.ago %>
+
+ # Ongoing Milestone
+ - name: Ongoing Milestone
+ group_id: <%= @group.id %>
+ group:
+ start_date: <%= 1.day.ago %>
+ due_date: <%= 1.month.from_now %>
+
+ # Future Milestone
+ - name: Ongoing Milestone
+ group_id: <%= @group.id %>
+ group:
+ start_date: <%= 1.month.from_now %>
+ due_date: <%= 2.months.from_now %>
+```
+
+#### Quirks
+
+- You _must_ specify `group:` and have it be empty. This is because the Milestones factory will manipulate the factory in an `after(:build)`. If this is not present, the Milestone will not be associated properly with the Group.
+
+### [Epics](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/spec/factories/epics.rb)
+
+```yaml
+epics:
+ # Simple Epic
+ - title: Simple Epic
+ group_id: <%= @group.id %>
+ author_id: <%= @owner.id %>
+
+ # Epic with detailed Markdown description
+ - title: Detailed Epic
+ group_id: <%= @group.id %>
+ author_id: <%= @owner.id %>
+ description: |
+ # Markdown
+
+ **Description**
+
+ # Epic with dates
+ - title: Epic with dates
+ group_id: <%= @group.id %>
+ author_id: <%= @owner.id %>
+ start_date: <%= 1.day.ago %>
+ due_date: <%= 1.month.from_now %>
+```
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 3155114d98a..bb4fa7f914c 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -195,12 +195,13 @@ Pipelines now include a Secret Detection job.
## Responding to a leaked secret
-Secrets detected by the analyzer should be immediately rotated.
-[Purging a file from the repository's history](../../project/repository/reducing_the_repo_size_using_git.md#purge-files-from-repository-history)
-may not be effective in removing all references to the file. Additionally, the secret will remain in any existing
-forks or clones of the repository.
+When a secret is detected, you should rotate it immediately. GitLab attempts to
+[automatically revoke](post_processing.md) some types of leaked secrets. For those that are not
+automatically revoked, you must do so manually.
-GitLab will attempt to [automatically revoke](post_processing.md) some types of leaked secrets.
+[Purging a secret from the repository's history](../../project/repository/reducing_the_repo_size_using_git.md#purge-files-from-repository-history)
+does not fully address the leak. The original secret remains in any existing forks or
+clones of the repository.
## Pinning to specific analyzer version
diff --git a/doc/user/group/reporting/git_abuse_rate_limit.md b/doc/user/group/reporting/git_abuse_rate_limit.md
index f8f721b2fad..64a8d6947ef 100644
--- a/doc/user/group/reporting/git_abuse_rate_limit.md
+++ b/doc/user/group/reporting/git_abuse_rate_limit.md
@@ -13,7 +13,7 @@ On self-managed GitLab, by default this feature is not available. To make it ava
This is the group-level documentation. For self-managed instances, see the [administration documentation](../../admin_area/reporting/git_abuse_rate_limit.md).
-Git abuse rate limiting is a feature to automatically [ban users](#unban-a-user) who download or clone more than a specified number of repositories of a group in a given time frame. Banned users cannot access the top-level group or any of its non-public subgroups via HTTP or SSH. Access to unrelated groups is unaffected. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). Access to unrelated groups is unaffected.
+Git abuse rate limiting is a feature to automatically [ban users](#unban-a-user) who download or clone more than a specified number of repositories of a group in a given time frame. Banned users cannot access the top-level group or any of its non-public subgroups via HTTP or SSH. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). Access to unrelated groups is unaffected.
Git abuse rate limiting does not apply to top-level group owners, [deploy tokens](../../../user/project/deploy_tokens/index.md), or [deploy keys](../../../user/project/deploy_keys/index.md).
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 2204437f2ec..954c3cd9f9e 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -68,6 +68,9 @@ module API
@results = search_service.search_objects(preload_method)
end
+ search_results = search_service.search_results
+ bad_request!(search_results.error) if search_results.respond_to?(:failed?) && search_results.failed?
+
set_global_search_log_information(additional_params)
Gitlab::Metrics::GlobalSearchSlis.record_apdex(
diff --git a/lib/generators/gitlab/snowplow_event_definition_generator.rb b/lib/generators/gitlab/snowplow_event_definition_generator.rb
index 8baeb6ba8c7..b1a31541350 100644
--- a/lib/generators/gitlab/snowplow_event_definition_generator.rb
+++ b/lib/generators/gitlab/snowplow_event_definition_generator.rb
@@ -59,8 +59,10 @@ module Gitlab
File.join(EE_DIR, file_name)
end
+ # Example of file name
+ # 20230227000018_project_management_issue_title_changed.yml
def file_name
- name = remove_special_chars("#{Time.current.to_i}_#{event_category}_#{event_action}")
+ name = remove_special_chars("#{Time.now.utc.strftime('%Y%m%d%H%M%S')}_#{event_category}_#{event_action}")
"#{name[0..95]}.yml" # max 100 chars, see https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/2030#note_679501200
end
diff --git a/lib/gitlab/ci/pipeline/duration.rb b/lib/gitlab/ci/pipeline/duration.rb
index 11155280fdb..573d4c25b91 100644
--- a/lib/gitlab/ci/pipeline/duration.rb
+++ b/lib/gitlab/ci/pipeline/duration.rb
@@ -93,12 +93,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def from_pipeline(pipeline)
builds =
- if Feature.enabled?(:ci_use_downstream_pipeline_duration_for_calculation, pipeline.project)
- self_and_downstreams_builds_of_pipeline(pipeline)
- else
- pipeline.processables.latest
- .with_status(STATUSES).where.not(started_at: nil).order(:started_at)
- end
+ self_and_downstreams_builds_of_pipeline(pipeline)
from_builds(builds)
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 37414f9e2b1..93befc2df57 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -120,6 +120,14 @@ module Gitlab
[]
end
+ def failed?
+ false
+ end
+
+ def error
+ nil
+ end
+
private
def collection_for(scope)
diff --git a/lib/tasks/gitlab/x509/update.rake b/lib/tasks/gitlab/x509/update.rake
index 7b7d15479bf..dc868ede05d 100644
--- a/lib/tasks/gitlab/x509/update.rake
+++ b/lib/tasks/gitlab/x509/update.rake
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'logger'
-
desc "GitLab | X509 | Update signatures when certificate store has changed"
namespace :gitlab do
namespace :x509 do
task update_signatures: :environment do
+ require 'logger'
+
update_certificates
end
diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake
index d2056338350..54aee5ff9f5 100644
--- a/lib/tasks/import.rake
+++ b/lib/tasks/import.rake
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-require 'benchmark'
-require 'rainbow/ext/string'
-
class GithubImport
def self.run!(...)
new(...).run!
@@ -124,6 +121,9 @@ class GithubRepos
end
namespace :import do
+ require 'benchmark'
+ require 'rainbow/ext/string'
+
desc 'GitLab | Import | Import a GitHub project - Example: import:github[ToKeN,root,root/blah,my/github_repo] (optional my/github_repo)'
task :github, [:token, :gitlab_username, :project_path] => :environment do |_t, args|
abort 'Project path must be: namespace(s)/project_name'.color(:red) unless args.project_path.include?('/')
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 266717412a6..76004776334 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19471,15 +19471,9 @@ msgstr ""
msgid "GlobalSearch|all GitLab"
msgstr ""
-msgid "GlobalSearch|group"
-msgstr ""
-
msgid "GlobalSearch|in %{scope}"
msgstr ""
-msgid "GlobalSearch|project"
-msgstr ""
-
msgid "GlobalShortcuts|Copied source branch name to clipboard."
msgstr ""
@@ -23402,9 +23396,6 @@ msgstr ""
msgid "InviteMembersModal|During your trial, you can invite as many members to %{groupName} as you like. When the trial ends, you'll have a maximum of %{dashboardLimit} members on the Free tier. To get more members, %{linkStart}upgrade to a paid plan%{linkEnd}."
msgstr ""
-msgid "InviteMembersModal|Explore paid plans"
-msgstr ""
-
msgid "InviteMembersModal|GitLab is better with colleagues!"
msgstr ""
@@ -32508,6 +32499,9 @@ msgstr ""
msgid "Preview payload"
msgstr ""
+msgid "Previous"
+msgstr ""
+
msgid "Previous Artifacts"
msgstr ""
@@ -41152,9 +41146,6 @@ msgstr ""
msgid "SourceEditor|No extension for unuse has been specified."
msgstr ""
-msgid "SourceEditor|Source Editor instance is required to set up an extension."
-msgstr ""
-
msgid "SourceEditor|`definition` property is expected on the extension."
msgstr ""
diff --git a/package.json b/package.json
index babf5a5d8f0..2f6732ccc0b 100644
--- a/package.json
+++ b/package.json
@@ -62,41 +62,41 @@
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",
"@sourcegraph/code-host-integration": "0.0.84",
- "@tiptap/core": "^2.0.0-beta.218",
- "@tiptap/extension-blockquote": "^2.0.0-beta.218",
- "@tiptap/extension-bold": "^2.0.0-beta.218",
- "@tiptap/extension-bubble-menu": "2.0.0-beta.218",
- "@tiptap/extension-bullet-list": "^2.0.0-beta.218",
- "@tiptap/extension-code": "^2.0.0-beta.218",
- "@tiptap/extension-code-block": "^2.0.0-beta.218",
- "@tiptap/extension-code-block-lowlight": "2.0.0-beta.218",
- "@tiptap/extension-document": "^2.0.0-beta.218",
- "@tiptap/extension-dropcursor": "^2.0.0-beta.218",
- "@tiptap/extension-gapcursor": "^2.0.0-beta.218",
- "@tiptap/extension-hard-break": "^2.0.0-beta.218",
- "@tiptap/extension-heading": "^2.0.0-beta.218",
- "@tiptap/extension-highlight": "^2.0.0-beta.218",
- "@tiptap/extension-history": "^2.0.0-beta.218",
- "@tiptap/extension-horizontal-rule": "^2.0.0-beta.218",
- "@tiptap/extension-image": "^2.0.0-beta.218",
- "@tiptap/extension-italic": "^2.0.0-beta.218",
- "@tiptap/extension-link": "^2.0.0-beta.218",
- "@tiptap/extension-list-item": "^2.0.0-beta.218",
- "@tiptap/extension-ordered-list": "^2.0.0-beta.218",
- "@tiptap/extension-paragraph": "^2.0.0-beta.218",
- "@tiptap/extension-strike": "^2.0.0-beta.218",
- "@tiptap/extension-subscript": "^2.0.0-beta.218",
- "@tiptap/extension-superscript": "^2.0.0-beta.218",
- "@tiptap/extension-table": "^2.0.0-beta.218",
- "@tiptap/extension-table-cell": "^2.0.0-beta.218",
- "@tiptap/extension-table-header": "^2.0.0-beta.218",
- "@tiptap/extension-table-row": "^2.0.0-beta.218",
- "@tiptap/extension-task-item": "^2.0.0-beta.218",
- "@tiptap/extension-task-list": "^2.0.0-beta.218",
- "@tiptap/extension-text": "^2.0.0-beta.218",
- "@tiptap/pm": "^2.0.0-beta.218",
- "@tiptap/suggestion": "^2.0.0-beta.218",
- "@tiptap/vue-2": "2.0.0-beta.218",
+ "@tiptap/core": "^2.0.0-beta.220",
+ "@tiptap/extension-blockquote": "^2.0.0-beta.220",
+ "@tiptap/extension-bold": "^2.0.0-beta.220",
+ "@tiptap/extension-bubble-menu": "2.0.0-beta.220",
+ "@tiptap/extension-bullet-list": "^2.0.0-beta.220",
+ "@tiptap/extension-code": "^2.0.0-beta.220",
+ "@tiptap/extension-code-block": "^2.0.0-beta.220",
+ "@tiptap/extension-code-block-lowlight": "2.0.0-beta.220",
+ "@tiptap/extension-document": "^2.0.0-beta.220",
+ "@tiptap/extension-dropcursor": "^2.0.0-beta.220",
+ "@tiptap/extension-gapcursor": "^2.0.0-beta.220",
+ "@tiptap/extension-hard-break": "^2.0.0-beta.220",
+ "@tiptap/extension-heading": "^2.0.0-beta.220",
+ "@tiptap/extension-highlight": "^2.0.0-beta.220",
+ "@tiptap/extension-history": "^2.0.0-beta.220",
+ "@tiptap/extension-horizontal-rule": "^2.0.0-beta.220",
+ "@tiptap/extension-image": "^2.0.0-beta.220",
+ "@tiptap/extension-italic": "^2.0.0-beta.220",
+ "@tiptap/extension-link": "^2.0.0-beta.220",
+ "@tiptap/extension-list-item": "^2.0.0-beta.220",
+ "@tiptap/extension-ordered-list": "^2.0.0-beta.220",
+ "@tiptap/extension-paragraph": "^2.0.0-beta.220",
+ "@tiptap/extension-strike": "^2.0.0-beta.220",
+ "@tiptap/extension-subscript": "^2.0.0-beta.220",
+ "@tiptap/extension-superscript": "^2.0.0-beta.220",
+ "@tiptap/extension-table": "^2.0.0-beta.220",
+ "@tiptap/extension-table-cell": "^2.0.0-beta.220",
+ "@tiptap/extension-table-header": "^2.0.0-beta.220",
+ "@tiptap/extension-table-row": "^2.0.0-beta.220",
+ "@tiptap/extension-task-item": "^2.0.0-beta.220",
+ "@tiptap/extension-task-list": "^2.0.0-beta.220",
+ "@tiptap/extension-text": "^2.0.0-beta.220",
+ "@tiptap/pm": "^2.0.0-beta.220",
+ "@tiptap/suggestion": "^2.0.0-beta.220",
+ "@tiptap/vue-2": "2.0.0-beta.220",
"apollo-upload-client": "15.0.0",
"apollo3-cache-persist": "^0.14.1",
"autosize": "^5.0.1",
diff --git a/scripts/api/update_issue.rb b/scripts/api/update_issue.rb
new file mode 100644
index 00000000000..ce296ebc358
--- /dev/null
+++ b/scripts/api/update_issue.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'gitlab'
+require_relative 'default_options'
+
+class UpdateIssue
+ def initialize(options)
+ @project = options.fetch(:project)
+
+ # Force the token to be a string so that if api_token is nil, it's set to '',
+ # allowing unauthenticated requests (for forks).
+ api_token = options.delete(:api_token).to_s
+
+ warn "No API token given." if api_token.empty?
+
+ @client = Gitlab.client(
+ endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
+ private_token: api_token
+ )
+ end
+
+ def execute(issue_iid, issue_data)
+ client.edit_issue(project, issue_iid, issue_data)
+ end
+
+ private
+
+ attr_reader :project, :client
+end
diff --git a/scripts/pipeline/create_test_failure_issues.rb b/scripts/pipeline/create_test_failure_issues.rb
new file mode 100755
index 00000000000..6312d392760
--- /dev/null
+++ b/scripts/pipeline/create_test_failure_issues.rb
@@ -0,0 +1,224 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require 'optparse'
+require 'json'
+require 'httparty'
+
+require_relative '../api/create_issue'
+require_relative '../api/find_issues'
+require_relative '../api/update_issue'
+
+class CreateTestFailureIssues
+ DEFAULT_OPTIONS = {
+ project: nil,
+ tests_report_file: 'tests_report.json',
+ issue_json_folder: 'tmp/issues/'
+ }.freeze
+
+ def initialize(options)
+ @options = options
+ end
+
+ def execute
+ puts "[CreateTestFailureIssues] No failed tests!" if failed_tests.empty?
+
+ failed_tests.each_with_object([]) do |failed_test, existing_issues|
+ CreateTestFailureIssue.new(options.dup).comment_or_create(failed_test, existing_issues).tap do |issue|
+ existing_issues << issue
+ File.write(File.join(options[:issue_json_folder], "issue-#{issue.iid}.json"), JSON.pretty_generate(issue.to_h))
+ end
+ end
+ end
+
+ private
+
+ attr_reader :options
+
+ def failed_tests
+ @failed_tests ||=
+ if File.exist?(options[:tests_report_file])
+ JSON.parse(File.read(options[:tests_report_file]))
+ else
+ puts "[CreateTestFailureIssues] #{options[:tests_report_file]} doesn't exist!"
+ []
+ end
+ end
+end
+
+class CreateTestFailureIssue
+ MAX_TITLE_LENGTH = 255
+ WWW_GITLAB_COM_SITE = 'https://about.gitlab.com'
+ WWW_GITLAB_COM_GROUPS_JSON = "#{WWW_GITLAB_COM_SITE}/groups.json".freeze
+ WWW_GITLAB_COM_CATEGORIES_JSON = "#{WWW_GITLAB_COM_SITE}/categories.json".freeze
+ FEATURE_CATEGORY_METADATA_REGEX = /(?<=feature_category: :)\w+/
+ DEFAULT_LABELS = ['type::maintenance', 'failure::flaky-test'].freeze
+
+ def initialize(options)
+ @project = options.delete(:project)
+ @api_token = options.delete(:api_token)
+ end
+
+ def comment_or_create(failed_test, existing_issues = [])
+ existing_issue = find(failed_test, existing_issues)
+
+ if existing_issue
+ update_reports(existing_issue, failed_test)
+ existing_issue
+ else
+ create(failed_test)
+ end
+ end
+
+ def find(failed_test, existing_issues = [])
+ failed_test_issue_title = failed_test_issue_title(failed_test)
+ issue_from_existing_issues = existing_issues.find { |issue| issue.title == failed_test_issue_title }
+ issue_from_issue_tracker = FindIssues
+ .new(project: project, api_token: api_token)
+ .execute(state: 'opened', search: failed_test_issue_title)
+ .first
+
+ existing_issue = issue_from_existing_issues || issue_from_issue_tracker
+
+ return unless existing_issue
+
+ puts "[CreateTestFailureIssue] Found issue '#{existing_issue.title}': #{existing_issue.web_url}!"
+
+ existing_issue
+ end
+
+ def update_reports(existing_issue, failed_test)
+ new_issue_description = "#{existing_issue.description}\n- #{failed_test['job_url']} (#{ENV['CI_PIPELINE_URL']})"
+ UpdateIssue
+ .new(project: project, api_token: api_token)
+ .execute(existing_issue.iid, description: new_issue_description)
+ puts "[CreateTestFailureIssue] Added a report in '#{existing_issue.title}': #{existing_issue.web_url}!"
+ end
+
+ def create(failed_test)
+ payload = {
+ title: failed_test_issue_title(failed_test),
+ description: failed_test_issue_description(failed_test),
+ labels: failed_test_issue_labels(failed_test)
+ }
+
+ CreateIssue.new(project: project, api_token: api_token).execute(payload).tap do |issue|
+ puts "[CreateTestFailureIssue] Created issue '#{issue.title}': #{issue.web_url}!"
+ end
+ end
+
+ private
+
+ attr_reader :project, :api_token
+
+ def failed_test_id(failed_test)
+ Digest::SHA256.hexdigest(search_safe(failed_test['name']))[0...12]
+ end
+
+ def failed_test_issue_title(failed_test)
+ title = "#{failed_test['file']} - ID: #{failed_test_id(failed_test)}"
+
+ raise "Title is too long!" if title.size > MAX_TITLE_LENGTH
+
+ title
+ end
+
+ def failed_test_issue_description(failed_test)
+ <<~DESCRIPTION
+ ### Full description
+
+ `#{search_safe(failed_test['name'])}`
+
+ ### File path
+
+ `#{failed_test['file']}`
+
+ <!-- Don't add anything after the report list since it's updated automatically -->
+ ### Reports
+
+ - #{failed_test['job_url']} (#{ENV['CI_PIPELINE_URL']})
+ DESCRIPTION
+ end
+
+ def failed_test_issue_labels(failed_test)
+ labels = DEFAULT_LABELS + category_and_group_labels_for_test_file(failed_test['file'])
+
+ # make sure we don't spam people who are notified to actual labels
+ labels.map { |label| "wip-#{label}" }
+ end
+
+ def category_and_group_labels_for_test_file(test_file)
+ feature_categories = File.open(File.expand_path(File.join('..', '..', test_file), __dir__))
+ .read
+ .scan(FEATURE_CATEGORY_METADATA_REGEX)
+
+ category_labels = feature_categories.filter_map { |category| categories_mapping.dig(category, 'label') }.uniq
+
+ groups = feature_categories.filter_map { |category| categories_mapping.dig(category, 'group') }
+ group_labels = groups.map { |group| groups_mapping.dig(group, 'label') }.uniq
+
+ (category_labels + [group_labels.first]).compact
+ end
+
+ def categories_mapping
+ @categories_mapping ||= self.class.fetch_json(WWW_GITLAB_COM_CATEGORIES_JSON)
+ end
+
+ def groups_mapping
+ @groups_mapping ||= self.class.fetch_json(WWW_GITLAB_COM_GROUPS_JSON)
+ end
+
+ def search_safe(value)
+ value.delete('"')
+ end
+
+ def self.fetch_json(json_url)
+ json = with_retries { HTTParty.get(json_url, format: :plain) } # rubocop:disable Gitlab/HTTParty
+ JSON.parse(json)
+ end
+
+ def self.with_retries(attempts: 3)
+ yield
+ rescue Errno::ECONNRESET, OpenSSL::SSL::SSLError, Net::OpenTimeout
+ retry if (attempts -= 1) > 0
+ raise
+ end
+ private_class_method :with_retries
+end
+
+if $PROGRAM_NAME == __FILE__
+ options = CreateTestFailureIssues::DEFAULT_OPTIONS.dup
+
+ OptionParser.new do |opts|
+ opts.on("-p", "--project PROJECT", String,
+ "Project where to create the issue (defaults to " \
+ "`#{CreateTestFailureIssues::DEFAULT_OPTIONS[:project]}`)") do |value|
+ options[:project] = value
+ end
+
+ opts.on("-r", "--tests-report-file file_path", String,
+ "Path to a JSON file which contains the current pipeline's tests report (defaults to " \
+ "`#{CreateTestFailureIssues::DEFAULT_OPTIONS[:tests_report_file]}`)"
+ ) do |value|
+ options[:tests_report_file] = value
+ end
+
+ opts.on("-f", "--issues-json-folder file_path", String,
+ "Path to a folder where to save the issues JSON data (defaults to " \
+ "`#{CreateTestFailureIssues::DEFAULT_OPTIONS[:issue_json_folder]}`)") do |value|
+ options[:issue_json_folder] = value
+ end
+
+ opts.on("-t", "--api-token API_TOKEN", String,
+ "A valid Project token with the `Reporter` role and `api` scope to create the issue") do |value|
+ options[:api_token] = value
+ end
+
+ opts.on("-h", "--help", "Prints this help") do
+ puts opts
+ exit
+ end
+ end.parse!
+
+ CreateTestFailureIssues.new(options).execute
+end
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index 0647058d63a..200eebfa7ae 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -98,11 +98,6 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
- factory :clusters_applications_crossplane, class: 'Clusters::Applications::Crossplane' do
- stack { 'gcp' }
- cluster factory: %i(cluster with_installed_helm provided_by_gcp)
- end
-
factory :clusters_applications_prometheus, class: 'Clusters::Applications::Prometheus' do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 32cd6beb7ea..8ae277af637 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -94,7 +94,6 @@ FactoryBot.define do
trait :with_all_applications do
application_helm factory: %i(clusters_applications_helm installed)
application_ingress factory: %i(clusters_applications_ingress installed)
- application_crossplane factory: %i(clusters_applications_crossplane installed)
application_prometheus factory: %i(clusters_applications_prometheus installed)
application_runner factory: %i(clusters_applications_runner installed)
application_jupyter factory: %i(clusters_applications_jupyter installed)
diff --git a/spec/features/admin_variables_spec.rb b/spec/features/admin_variables_spec.rb
index 0c9c4d19791..274e62defd9 100644
--- a/spec/features/admin_variables_spec.rb
+++ b/spec/features/admin_variables_spec.rb
@@ -12,9 +12,21 @@ RSpec.describe 'Instance variables', :js, feature_category: :pipeline_compositio
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
+
visit page_path
wait_for_requests
end
- it_behaves_like 'variable list', isAdmin: true
+ context 'when ci_variables_pages FF is enabled' do
+ it_behaves_like 'variable list', is_admin: true
+ it_behaves_like 'variable list pagination', :ci_instance_variable
+ end
+
+ context 'when ci_variables_pages FF is disabled' do
+ before do
+ stub_feature_flags(ci_variables_pages: false)
+ end
+
+ it_behaves_like 'variable list', is_admin: true
+ end
end
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
index d34accee0b8..8644a15a093 100644
--- a/spec/features/group_variables_spec.rb
+++ b/spec/features/group_variables_spec.rb
@@ -15,5 +15,16 @@ RSpec.describe 'Group variables', :js, feature_category: :pipeline_composition d
wait_for_requests
end
- it_behaves_like 'variable list'
+ context 'when ci_variables_pages FF is enabled' do
+ it_behaves_like 'variable list'
+ it_behaves_like 'variable list pagination', :ci_group_variable
+ end
+
+ context 'when ci_variables_pages FF is disabled' do
+ before do
+ stub_feature_flags(ci_variables_pages: false)
+ end
+
+ it_behaves_like 'variable list'
+ end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 350b0582565..c979aff2147 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe 'issuable list', :js, feature_category: :team_planning do
end
end
- it 'displays a warning if counting the number of issues times out' do
+ it 'displays a warning if counting the number of issues times out', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393344' do
allow_any_instance_of(IssuesFinder).to receive(:count_by_state).and_raise(ActiveRecord::QueryCanceled)
visit_issuable_list(:issue)
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 66b87148eb2..9ab53a00903 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'User comments on a diff', :js, feature_category: :code_review_wo
end
context 'in multiple files' do
- it 'toggles comments' do
+ it 'toggles comments', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393518' do
first_line_element = find_by_scrolling("[id='#{sample_compare.changes[0][:line_code]}']").find(:xpath, "..")
first_root_element = first_line_element.ancestor('[data-path]')
click_diff_line(first_line_element)
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index ae506a4bad7..69b8408dcd6 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -16,7 +16,18 @@ RSpec.describe 'Project variables', :js, feature_category: :pipeline_composition
wait_for_requests
end
- it_behaves_like 'variable list'
+ context 'when ci_variables_pages FF is enabled' do
+ it_behaves_like 'variable list'
+ it_behaves_like 'variable list pagination', :ci_variable
+ end
+
+ context 'when ci_variables_pages FF is disabled' do
+ before do
+ stub_feature_flags(ci_variables_pages: false)
+ end
+
+ it_behaves_like 'variable list'
+ end
it 'adds a new variable with an environment scope' do
click_button('Add variable')
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js
index 32af2ec4de9..b4d77edf154 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js
@@ -22,6 +22,7 @@ describe('Ci variable table', () => {
hideEnvironmentScope: false,
isLoading: false,
maxVariableLimit: 5,
+ pageInfo: { after: '' },
variables: mockVariablesWithScopes(projectString),
};
@@ -49,6 +50,7 @@ describe('Ci variable table', () => {
entity: 'project',
isLoading: defaultProps.isLoading,
maxVariableLimit: defaultProps.maxVariableLimit,
+ pageInfo: defaultProps.pageInfo,
variables: defaultProps.variables,
});
});
@@ -144,4 +146,22 @@ describe('Ci variable table', () => {
expect(wrapper.emitted(eventName)).toEqual([[newVariable]]);
});
});
+
+ describe('pages events', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each`
+ eventName | args
+ ${'handle-prev-page'} | ${undefined}
+ ${'handle-next-page'} | ${undefined}
+ ${'sort-changed'} | ${{ sortDesc: true }}
+ `('bubbles up the $eventName event', async ({ args, eventName }) => {
+ findCiVariableTable().vm.$emit(eventName, args);
+ await nextTick();
+
+ expect(wrapper.emitted(eventName)).toEqual([[args]]);
+ });
+ });
});
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js
index c977ae773db..6e6ef7e9a8a 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js
@@ -53,6 +53,7 @@ const mockProvide = {
const defaultProps = {
areScopedVariablesAvailable: true,
+ pageInfo: {},
hideEnvironmentScope: false,
refetchAfterMutation: false,
};
@@ -105,345 +106,378 @@ describe('Ci Variable Shared Component', () => {
mockVariables = jest.fn();
});
- describe('while queries are being fetch', () => {
- beforeEach(() => {
- createComponentWithApollo({ isLoading: true });
- });
-
- it('shows a loading icon', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- expect(findCiTable().exists()).toBe(false);
- });
- });
-
- describe('when queries are resolved', () => {
- describe('successfully', () => {
- beforeEach(async () => {
- mockEnvironments.mockResolvedValue(mockProjectEnvironments);
- mockVariables.mockResolvedValue(mockProjectVariables);
-
- await createComponentWithApollo({ provide: createProjectProvide() });
+ describe.each`
+ isVariablePagesEnabled | text
+ ${true} | ${'enabled'}
+ ${false} | ${'disabled'}
+ `('When Pages FF is $text', ({ isVariablePagesEnabled }) => {
+ const featureFlagProvide = isVariablePagesEnabled
+ ? { glFeatures: { ciVariablesPages: true } }
+ : {};
+
+ describe('while queries are being fetch', () => {
+ beforeEach(() => {
+ createComponentWithApollo({ isLoading: true });
});
- it('passes down the expected max variable limit as props', () => {
- expect(findCiSettings().props('maxVariableLimit')).toBe(
- mockProjectVariables.data.project.ciVariables.limit,
- );
+ it('shows a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findCiTable().exists()).toBe(false);
});
+ });
- it('passes down the expected environments as props', () => {
- expect(findCiSettings().props('environments')).toEqual([prodName, devName]);
- });
+ describe('when queries are resolved', () => {
+ describe('successfully', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockResolvedValue(mockProjectVariables);
- it('passes down the expected variables as props', () => {
- expect(findCiSettings().props('variables')).toEqual(
- mockProjectVariables.data.project.ciVariables.nodes,
- );
- });
+ await createComponentWithApollo({
+ provide: { ...createProjectProvide(), ...featureFlagProvide },
+ });
+ });
- it('createAlert was not called', () => {
- expect(createAlert).not.toHaveBeenCalled();
- });
- });
+ it('passes down the expected max variable limit as props', () => {
+ expect(findCiSettings().props('maxVariableLimit')).toBe(
+ mockProjectVariables.data.project.ciVariables.limit,
+ );
+ });
- describe('with an error for variables', () => {
- beforeEach(async () => {
- mockEnvironments.mockResolvedValue(mockProjectEnvironments);
- mockVariables.mockRejectedValue();
+ it('passes down the expected environments as props', () => {
+ expect(findCiSettings().props('environments')).toEqual([prodName, devName]);
+ });
- await createComponentWithApollo();
- });
+ it('passes down the expected variables as props', () => {
+ expect(findCiSettings().props('variables')).toEqual(
+ mockProjectVariables.data.project.ciVariables.nodes,
+ );
+ });
- it('calls createAlert with the expected error message', () => {
- expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText });
+ it('createAlert was not called', () => {
+ expect(createAlert).not.toHaveBeenCalled();
+ });
});
- });
- describe('with an error for environments', () => {
- beforeEach(async () => {
- mockEnvironments.mockRejectedValue();
- mockVariables.mockResolvedValue(mockProjectVariables);
+ describe('with an error for variables', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockRejectedValue();
- await createComponentWithApollo();
- });
+ await createComponentWithApollo({ provide: featureFlagProvide });
+ });
- it('calls createAlert with the expected error message', () => {
- expect(createAlert).toHaveBeenCalledWith({ message: environmentFetchErrorText });
+ it('calls createAlert with the expected error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText });
+ });
});
- });
- });
- describe('environment query', () => {
- describe('when there is an environment key in queryData', () => {
- beforeEach(async () => {
- mockEnvironments.mockResolvedValue(mockProjectEnvironments);
- mockVariables.mockResolvedValue(mockProjectVariables);
+ describe('with an error for environments', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockRejectedValue();
+ mockVariables.mockResolvedValue(mockProjectVariables);
- await createComponentWithApollo({ props: { ...createProjectProps() } });
- });
+ await createComponentWithApollo({ provide: featureFlagProvide });
+ });
- it('is executed', () => {
- expect(mockVariables).toHaveBeenCalled();
+ it('calls createAlert with the expected error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: environmentFetchErrorText });
+ });
});
});
- describe('when there isnt an environment key in queryData', () => {
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
+ describe('environment query', () => {
+ describe('when there is an environment key in queryData', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockResolvedValue(mockProjectVariables);
- await createComponentWithApollo({ props: { ...createGroupProps() } });
- });
+ await createComponentWithApollo({
+ props: { ...createProjectProps() },
+ provide: featureFlagProvide,
+ });
+ });
- it('is skipped', () => {
- expect(mockVariables).not.toHaveBeenCalled();
+ it('is executed', () => {
+ expect(mockVariables).toHaveBeenCalled();
+ });
});
- });
- });
- describe('mutations', () => {
- const groupProps = createGroupProps();
+ describe('when there isnt an environment key in queryData', () => {
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockGroupVariables);
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
+ await createComponentWithApollo({
+ props: { ...createGroupProps() },
+ provide: featureFlagProvide,
+ });
+ });
- await createComponentWithApollo({
- customHandlers: [[getGroupVariables, mockVariables]],
- props: groupProps,
+ it('is skipped', () => {
+ expect(mockVariables).not.toHaveBeenCalled();
+ });
});
});
- it.each`
- actionName | mutation | event
- ${'add'} | ${groupProps.mutationData[ADD_MUTATION_ACTION]} | ${'add-variable'}
- ${'update'} | ${groupProps.mutationData[UPDATE_MUTATION_ACTION]} | ${'update-variable'}
- ${'delete'} | ${groupProps.mutationData[DELETE_MUTATION_ACTION]} | ${'delete-variable'}
- `(
- 'calls the right mutation from propsData when user performs $actionName variable',
- async ({ event, mutation }) => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
-
- await findCiSettings().vm.$emit(event, newVariable);
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation,
- variables: {
- endpoint: mockProvide.endpoint,
- fullPath: groupProps.fullPath,
- id: convertToGraphQLId(TYPENAME_GROUP, groupProps.id),
- variable: newVariable,
- },
- });
- },
- );
-
- it.each`
- actionName | event
- ${'add'} | ${'add-variable'}
- ${'update'} | ${'update-variable'}
- ${'delete'} | ${'delete-variable'}
- `(
- 'throws with the specific graphql error if present when user performs $actionName variable',
- async ({ event }) => {
- const graphQLErrorMessage = 'There is a problem with this graphQL action';
- jest
- .spyOn(wrapper.vm.$apollo, 'mutate')
- .mockResolvedValue({ data: { ciVariableMutation: { errors: [graphQLErrorMessage] } } });
- await findCiSettings().vm.$emit(event, newVariable);
- await nextTick();
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
- expect(createAlert).toHaveBeenCalledWith({ message: graphQLErrorMessage });
- },
- );
-
- it.each`
- actionName | event
- ${'add'} | ${'add-variable'}
- ${'update'} | ${'update-variable'}
- ${'delete'} | ${'delete-variable'}
- `(
- 'throws generic error on failure with no graphql errors and user performs $actionName variable',
- async ({ event }) => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => {
- throw new Error();
- });
- await findCiSettings().vm.$emit(event, newVariable);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
- expect(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText });
- },
- );
+ describe('mutations', () => {
+ const groupProps = createGroupProps();
- describe('without fullpath and ID props', () => {
beforeEach(async () => {
- mockVariables.mockResolvedValue(mockAdminVariables);
+ mockVariables.mockResolvedValue(mockGroupVariables);
await createComponentWithApollo({
- customHandlers: [[getAdminVariables, mockVariables]],
- props: createInstanceProps(),
+ customHandlers: [[getGroupVariables, mockVariables]],
+ props: groupProps,
+ provide: featureFlagProvide,
});
});
-
- it('does not pass fullPath and ID to the mutation', async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
-
- await findCiSettings().vm.$emit('add-variable', newVariable);
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: wrapper.props().mutationData[ADD_MUTATION_ACTION],
- variables: {
- endpoint: mockProvide.endpoint,
- variable: newVariable,
- },
- });
- });
- });
- });
-
- describe('Props', () => {
- const mockGroupCiVariables = mockGroupVariables.data.group.ciVariables;
- const mockProjectCiVariables = mockProjectVariables.data.project.ciVariables;
-
- describe('in a specific context as', () => {
it.each`
- name | mockVariablesValue | mockEnvironmentsValue | withEnvironments | expectedEnvironments | propsFn | provideFn | mutation | maxVariableLimit
- ${'project'} | ${mockProjectVariables} | ${mockProjectEnvironments} | ${true} | ${['prod', 'dev']} | ${createProjectProps} | ${createProjectProvide} | ${null} | ${mockProjectCiVariables.limit}
- ${'group'} | ${mockGroupVariables} | ${[]} | ${false} | ${[]} | ${createGroupProps} | ${createGroupProvide} | ${getGroupVariables} | ${mockGroupCiVariables.limit}
- ${'instance'} | ${mockAdminVariables} | ${[]} | ${false} | ${[]} | ${createInstanceProps} | ${() => {}} | ${getAdminVariables} | ${0}
+ actionName | mutation | event
+ ${'add'} | ${groupProps.mutationData[ADD_MUTATION_ACTION]} | ${'add-variable'}
+ ${'update'} | ${groupProps.mutationData[UPDATE_MUTATION_ACTION]} | ${'update-variable'}
+ ${'delete'} | ${groupProps.mutationData[DELETE_MUTATION_ACTION]} | ${'delete-variable'}
`(
- 'passes down all the required props when its a $name component',
- async ({
- mutation,
- maxVariableLimit,
- mockVariablesValue,
- mockEnvironmentsValue,
- withEnvironments,
- expectedEnvironments,
- propsFn,
- provideFn,
- }) => {
- const props = propsFn();
- const provide = provideFn();
-
- mockVariables.mockResolvedValue(mockVariablesValue);
-
- if (withEnvironments) {
- mockEnvironments.mockResolvedValue(mockEnvironmentsValue);
- }
-
- let customHandlers = null;
-
- if (mutation) {
- customHandlers = [[mutation, mockVariables]];
- }
+ 'calls the right mutation from propsData when user performs $actionName variable',
+ async ({ event, mutation }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation,
+ variables: {
+ endpoint: mockProvide.endpoint,
+ fullPath: groupProps.fullPath,
+ id: convertToGraphQLId(TYPENAME_GROUP, groupProps.id),
+ variable: newVariable,
+ },
+ });
+ },
+ );
- await createComponentWithApollo({ customHandlers, props, provide });
+ it.each`
+ actionName | event
+ ${'add'} | ${'add-variable'}
+ ${'update'} | ${'update-variable'}
+ ${'delete'} | ${'delete-variable'}
+ `(
+ 'throws with the specific graphql error if present when user performs $actionName variable',
+ async ({ event }) => {
+ const graphQLErrorMessage = 'There is a problem with this graphQL action';
+ jest
+ .spyOn(wrapper.vm.$apollo, 'mutate')
+ .mockResolvedValue({ data: { ciVariableMutation: { errors: [graphQLErrorMessage] } } });
+ await findCiSettings().vm.$emit(event, newVariable);
+ await nextTick();
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalledWith({ message: graphQLErrorMessage });
+ },
+ );
- expect(findCiSettings().props()).toEqual({
- areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable,
- hideEnvironmentScope: defaultProps.hideEnvironmentScope,
- isLoading: false,
- maxVariableLimit,
- variables: wrapper.props().queryData.ciVariables.lookup(mockVariablesValue.data)?.nodes,
- entity: props.entity,
- environments: expectedEnvironments,
+ it.each`
+ actionName | event
+ ${'add'} | ${'add-variable'}
+ ${'update'} | ${'update-variable'}
+ ${'delete'} | ${'delete-variable'}
+ `(
+ 'throws generic error on failure with no graphql errors and user performs $actionName variable',
+ async ({ event }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => {
+ throw new Error();
});
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText });
},
);
- });
- describe('refetchAfterMutation', () => {
- it.each`
- bool | text
- ${true} | ${'refetches the variables'}
- ${false} | ${'does not refetch the variables'}
- `('when $bool it $text', async ({ bool }) => {
- await createComponentWithApollo({
- props: { ...createInstanceProps(), refetchAfterMutation: bool },
- });
+ describe('without fullpath and ID props', () => {
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockAdminVariables);
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: {} });
- jest.spyOn(wrapper.vm.$apollo.queries.ciVariables, 'refetch').mockImplementation(jest.fn());
+ await createComponentWithApollo({
+ customHandlers: [[getAdminVariables, mockVariables]],
+ props: createInstanceProps(),
+ provide: featureFlagProvide,
+ });
+ });
- await findCiSettings().vm.$emit('add-variable', newVariable);
+ it('does not pass fullPath and ID to the mutation', async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
- await nextTick();
+ await findCiSettings().vm.$emit('add-variable', newVariable);
- if (bool) {
- expect(wrapper.vm.$apollo.queries.ciVariables.refetch).toHaveBeenCalled();
- } else {
- expect(wrapper.vm.$apollo.queries.ciVariables.refetch).not.toHaveBeenCalled();
- }
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: wrapper.props().mutationData[ADD_MUTATION_ACTION],
+ variables: {
+ endpoint: mockProvide.endpoint,
+ variable: newVariable,
+ },
+ });
+ });
});
});
- describe('Validators', () => {
- describe('queryData', () => {
- let error;
+ describe('Props', () => {
+ const mockGroupCiVariables = mockGroupVariables.data.group.ciVariables;
+ const mockProjectCiVariables = mockProjectVariables.data.project.ciVariables;
+
+ describe('in a specific context as', () => {
+ it.each`
+ name | mockVariablesValue | mockEnvironmentsValue | withEnvironments | expectedEnvironments | propsFn | provideFn | mutation | maxVariableLimit
+ ${'project'} | ${mockProjectVariables} | ${mockProjectEnvironments} | ${true} | ${['prod', 'dev']} | ${createProjectProps} | ${createProjectProvide} | ${null} | ${mockProjectCiVariables.limit}
+ ${'group'} | ${mockGroupVariables} | ${[]} | ${false} | ${[]} | ${createGroupProps} | ${createGroupProvide} | ${getGroupVariables} | ${mockGroupCiVariables.limit}
+ ${'instance'} | ${mockAdminVariables} | ${[]} | ${false} | ${[]} | ${createInstanceProps} | ${() => {}} | ${getAdminVariables} | ${0}
+ `(
+ 'passes down all the required props when its a $name component',
+ async ({
+ mutation,
+ maxVariableLimit,
+ mockVariablesValue,
+ mockEnvironmentsValue,
+ withEnvironments,
+ expectedEnvironments,
+ propsFn,
+ provideFn,
+ }) => {
+ const props = propsFn();
+ const provide = provideFn();
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
- });
+ mockVariables.mockResolvedValue(mockVariablesValue);
+
+ if (withEnvironments) {
+ mockEnvironments.mockResolvedValue(mockEnvironmentsValue);
+ }
+
+ let customHandlers = null;
+
+ if (mutation) {
+ customHandlers = [[mutation, mockVariables]];
+ }
- it('will mount component with right data', async () => {
- try {
await createComponentWithApollo({
- customHandlers: [[getGroupVariables, mockVariables]],
- props: { ...createGroupProps() },
+ customHandlers,
+ props,
+ provide: { ...provide, ...featureFlagProvide },
});
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(true);
- expect(error).toBeUndefined();
- }
- });
- it('will not mount component with wrong data', async () => {
- try {
- await createComponentWithApollo({
- customHandlers: [[getGroupVariables, mockVariables]],
- props: { ...createGroupProps(), queryData: { wrongKey: {} } },
+ expect(findCiSettings().props()).toEqual({
+ areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable,
+ hideEnvironmentScope: defaultProps.hideEnvironmentScope,
+ pageInfo: defaultProps.pageInfo,
+ isLoading: false,
+ maxVariableLimit,
+ variables: wrapper.props().queryData.ciVariables.lookup(mockVariablesValue.data)
+ ?.nodes,
+ entity: props.entity,
+ environments: expectedEnvironments,
});
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(false);
- expect(error.toString()).toContain('custom validator check failed for prop');
+ },
+ );
+ });
+
+ describe('refetchAfterMutation', () => {
+ it.each`
+ bool | text
+ ${true} | ${'refetches the variables'}
+ ${false} | ${'does not refetch the variables'}
+ `('when $bool it $text', async ({ bool }) => {
+ await createComponentWithApollo({
+ props: { ...createInstanceProps(), refetchAfterMutation: bool },
+ provide: featureFlagProvide,
+ });
+
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: {} });
+ jest
+ .spyOn(wrapper.vm.$apollo.queries.ciVariables, 'refetch')
+ .mockImplementation(jest.fn());
+
+ await findCiSettings().vm.$emit('add-variable', newVariable);
+
+ await nextTick();
+
+ if (bool) {
+ expect(wrapper.vm.$apollo.queries.ciVariables.refetch).toHaveBeenCalled();
+ } else {
+ expect(wrapper.vm.$apollo.queries.ciVariables.refetch).not.toHaveBeenCalled();
}
});
});
- describe('mutationData', () => {
- let error;
+ describe('Validators', () => {
+ describe('queryData', () => {
+ let error;
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
- });
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockGroupVariables);
+ });
- it('will mount component with right data', async () => {
- try {
- await createComponentWithApollo({
- props: { ...createGroupProps() },
- });
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(true);
- expect(error).toBeUndefined();
- }
+ it('will mount component with right data', async () => {
+ try {
+ await createComponentWithApollo({
+ customHandlers: [[getGroupVariables, mockVariables]],
+ props: { ...createGroupProps() },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(true);
+ expect(error).toBeUndefined();
+ }
+ });
+
+ it('will not mount component with wrong data', async () => {
+ try {
+ await createComponentWithApollo({
+ customHandlers: [[getGroupVariables, mockVariables]],
+ props: { ...createGroupProps(), queryData: { wrongKey: {} } },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(false);
+ expect(error.toString()).toContain('custom validator check failed for prop');
+ }
+ });
});
- it('will not mount component with wrong data', async () => {
- try {
- await createComponentWithApollo({
- props: { ...createGroupProps(), mutationData: { wrongKey: {} } },
- });
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(false);
- expect(error.toString()).toContain('custom validator check failed for prop');
- }
+ describe('mutationData', () => {
+ let error;
+
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockGroupVariables);
+ });
+
+ it('will mount component with right data', async () => {
+ try {
+ await createComponentWithApollo({
+ props: { ...createGroupProps() },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(true);
+ expect(error).toBeUndefined();
+ }
+ });
+
+ it('will not mount component with wrong data', async () => {
+ try {
+ await createComponentWithApollo({
+ props: { ...createGroupProps(), mutationData: { wrongKey: {} } },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(false);
+ expect(error.toString()).toContain('custom validator check failed for prop');
+ }
+ });
});
});
});
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
index 9e2508c56ee..2ef789e89c3 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
@@ -12,18 +12,25 @@ describe('Ci variable table', () => {
entity: 'project',
isLoading: false,
maxVariableLimit: mockVariables(projectString).length + 1,
+ pageInfo: {},
variables: mockVariables(projectString),
};
const mockMaxVariableLimit = defaultProps.variables.length;
- const createComponent = ({ props = {} } = {}) => {
+ const createComponent = ({ props = {}, provide = {} } = {}) => {
wrapper = mountExtended(CiVariableTable, {
attachTo: document.body,
propsData: {
...defaultProps,
...props,
},
+ provide: {
+ glFeatures: {
+ ciVariablesPages: false,
+ },
+ ...provide,
+ },
});
};
@@ -41,132 +48,136 @@ describe('Ci variable table', () => {
return sprintf(EXCEEDS_VARIABLE_LIMIT_TEXT, { entity, currentVariableCount, maxVariableLimit });
};
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('When table is empty', () => {
- beforeEach(() => {
- createComponent({ props: { variables: [] } });
- });
+ describe.each`
+ isVariablePagesEnabled | text
+ ${true} | ${'enabled'}
+ ${false} | ${'disabled'}
+ `('When Pages FF is $text', ({ isVariablePagesEnabled }) => {
+ const provide = isVariablePagesEnabled ? { glFeatures: { ciVariablesPages: true } } : {};
- it('displays empty message', () => {
- expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
- });
-
- it('hides the reveal button', () => {
- expect(findRevealButton().exists()).toBe(false);
- });
- });
+ describe('When table is empty', () => {
+ beforeEach(() => {
+ createComponent({ props: { variables: [] }, provide });
+ });
- describe('When table has variables', () => {
- beforeEach(() => {
- createComponent();
- });
+ it('displays empty message', () => {
+ expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
+ });
- it('does not display the empty message', () => {
- expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
+ it('hides the reveal button', () => {
+ expect(findRevealButton().exists()).toBe(false);
+ });
});
- it('displays the reveal button', () => {
- expect(findRevealButton().exists()).toBe(true);
- });
+ describe('When table has variables', () => {
+ beforeEach(() => {
+ createComponent({ provide });
+ });
- it('displays the correct amount of variables', async () => {
- expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(defaultProps.variables.length);
- });
+ it('does not display the empty message', () => {
+ expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
+ });
- it('displays the correct variable options', async () => {
- expect(findOptionsValues(0)).toBe('Protected, Expanded');
- expect(findOptionsValues(1)).toBe('Masked');
- });
+ it('displays the reveal button', () => {
+ expect(findRevealButton().exists()).toBe(true);
+ });
- it('enables the Add Variable button', () => {
- expect(findAddButton().props('disabled')).toBe(false);
- });
- });
+ it('displays the correct amount of variables', async () => {
+ expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(defaultProps.variables.length);
+ });
- describe('When variables have exceeded the max limit', () => {
- beforeEach(() => {
- createComponent({ props: { maxVariableLimit: mockVariables(projectString).length } });
- });
+ it('displays the correct variable options', async () => {
+ expect(findOptionsValues(0)).toBe('Protected, Expanded');
+ expect(findOptionsValues(1)).toBe('Masked');
+ });
- it('disables the Add Variable button', () => {
- expect(findAddButton().props('disabled')).toBe(true);
+ it('enables the Add Variable button', () => {
+ expect(findAddButton().props('disabled')).toBe(false);
+ });
});
- });
- describe('max limit reached alert', () => {
- describe('when there is no variable limit', () => {
+ describe('When variables have exceeded the max limit', () => {
beforeEach(() => {
createComponent({
- props: { maxVariableLimit: 0 },
+ props: { maxVariableLimit: mockVariables(projectString).length },
+ provide,
});
});
- it('hides alert', () => {
- expect(findLimitReachedAlerts().length).toBe(0);
+ it('disables the Add Variable button', () => {
+ expect(findAddButton().props('disabled')).toBe(true);
});
});
- describe('when variable limit exists', () => {
- it('hides alert when limit has not been reached', () => {
- createComponent();
+ describe('max limit reached alert', () => {
+ describe('when there is no variable limit', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { maxVariableLimit: 0 },
+ provide,
+ });
+ });
- expect(findLimitReachedAlerts().length).toBe(0);
+ it('hides alert', () => {
+ expect(findLimitReachedAlerts().length).toBe(0);
+ });
});
- it('shows alert when limit has been reached', () => {
- const exceedsVariableLimitText = generateExceedsVariableLimitText(
- defaultProps.entity,
- defaultProps.variables.length,
- mockMaxVariableLimit,
- );
+ describe('when variable limit exists', () => {
+ it('hides alert when limit has not been reached', () => {
+ createComponent({ provide });
- createComponent({
- props: { maxVariableLimit: mockMaxVariableLimit },
+ expect(findLimitReachedAlerts().length).toBe(0);
});
- expect(findLimitReachedAlerts().length).toBe(2);
+ it('shows alert when limit has been reached', () => {
+ const exceedsVariableLimitText = generateExceedsVariableLimitText(
+ defaultProps.entity,
+ defaultProps.variables.length,
+ mockMaxVariableLimit,
+ );
+
+ createComponent({
+ props: { maxVariableLimit: mockMaxVariableLimit },
+ });
- expect(findLimitReachedAlerts().at(0).props('dismissible')).toBe(false);
- expect(findLimitReachedAlerts().at(0).text()).toContain(exceedsVariableLimitText);
+ expect(findLimitReachedAlerts().length).toBe(2);
- expect(findLimitReachedAlerts().at(1).props('dismissible')).toBe(false);
- expect(findLimitReachedAlerts().at(1).text()).toContain(exceedsVariableLimitText);
+ expect(findLimitReachedAlerts().at(0).props('dismissible')).toBe(false);
+ expect(findLimitReachedAlerts().at(0).text()).toContain(exceedsVariableLimitText);
+
+ expect(findLimitReachedAlerts().at(1).props('dismissible')).toBe(false);
+ expect(findLimitReachedAlerts().at(1).text()).toContain(exceedsVariableLimitText);
+ });
});
});
- });
- describe('Table click actions', () => {
- beforeEach(() => {
- createComponent();
- });
+ describe('Table click actions', () => {
+ beforeEach(() => {
+ createComponent({ provide });
+ });
- it('reveals secret values when button is clicked', async () => {
- expect(findHiddenValues()).toHaveLength(defaultProps.variables.length);
- expect(findRevealedValues()).toHaveLength(0);
+ it('reveals secret values when button is clicked', async () => {
+ expect(findHiddenValues()).toHaveLength(defaultProps.variables.length);
+ expect(findRevealedValues()).toHaveLength(0);
- await findRevealButton().trigger('click');
+ await findRevealButton().trigger('click');
- expect(findHiddenValues()).toHaveLength(0);
- expect(findRevealedValues()).toHaveLength(defaultProps.variables.length);
- });
+ expect(findHiddenValues()).toHaveLength(0);
+ expect(findRevealedValues()).toHaveLength(defaultProps.variables.length);
+ });
- it('dispatches `setSelectedVariable` with correct variable to edit', async () => {
- await findEditButton().trigger('click');
+ it('dispatches `setSelectedVariable` with correct variable to edit', async () => {
+ await findEditButton().trigger('click');
- expect(wrapper.emitted('set-selected-variable')).toEqual([[defaultProps.variables[0]]]);
- });
+ expect(wrapper.emitted('set-selected-variable')).toEqual([[defaultProps.variables[0]]]);
+ });
- it('dispatches `setSelectedVariable` with no variable when adding a new one', async () => {
- await findAddButton().trigger('click');
+ it('dispatches `setSelectedVariable` with no variable when adding a new one', async () => {
+ await findAddButton().trigger('click');
- expect(wrapper.emitted('set-selected-variable')).toEqual([[null]]);
+ expect(wrapper.emitted('set-selected-variable')).toEqual([[null]]);
+ });
});
});
});
diff --git a/spec/frontend/commit/pipelines/pipelines_table_spec.js b/spec/frontend/commit/pipelines/pipelines_table_spec.js
index 4bffb6a0fd3..4b8abc8cf99 100644
--- a/spec/frontend/commit/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js
@@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import fixture from 'test_fixtures/pipelines/pipelines.json';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
import PipelinesTable from '~/commit/pipelines/pipelines_table.vue';
@@ -27,6 +28,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
let wrapper;
let pipeline;
let mock;
+ const showMock = jest.fn();
const findRunPipelineBtn = () => wrapper.findByTestId('run_pipeline_button');
const findRunPipelineBtnMobile = () => wrapper.findByTestId('run_pipeline_button_mobile');
@@ -38,7 +40,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findMrPipelinesDocsLink = () => wrapper.findByTestId('mr-pipelines-docs-link');
- const createComponent = (props = {}) => {
+ const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: {
@@ -50,6 +52,12 @@ describe('Pipelines table in Commits and Merge requests', () => {
mocks: {
$toast,
},
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ template: '<div />',
+ methods: { show: showMock },
+ }),
+ },
}),
);
};
@@ -62,11 +70,6 @@ describe('Pipelines table in Commits and Merge requests', () => {
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
});
- afterEach(() => {
- wrapper.destroy();
- mock.restore();
- });
-
describe('successful request', () => {
describe('without pipelines', () => {
beforeEach(async () => {
@@ -95,6 +98,35 @@ describe('Pipelines table in Commits and Merge requests', () => {
});
});
+ describe('with pagination', () => {
+ beforeEach(async () => {
+ mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipeline], {
+ 'X-TOTAL': 10,
+ 'X-PER-PAGE': 2,
+ 'X-PAGE': 1,
+ 'X-TOTAL-PAGES': 5,
+ 'X-NEXT-PAGE': 2,
+ 'X-PREV-PAGE': 2,
+ });
+
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('should make an API request when using pagination', async () => {
+ expect(mock.history.get).toHaveLength(1);
+ expect(mock.history.get[0].params.page).toBe('1');
+
+ wrapper.find('.next-page-item').trigger('click');
+
+ await waitForPromises();
+
+ expect(mock.history.get).toHaveLength(2);
+ expect(mock.history.get[1].params.page).toBe('2');
+ });
+ });
+
describe('with pipelines', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipeline], { 'x-total': 10 });
@@ -111,32 +143,6 @@ describe('Pipelines table in Commits and Merge requests', () => {
expect(findErrorEmptyState().exists()).toBe(false);
});
- describe('with pagination', () => {
- it('should make an API request when using pagination', async () => {
- jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- await wrapper.setData({
- store: {
- state: {
- pageInfo: {
- page: 1,
- total: 10,
- perPage: 2,
- nextPage: 2,
- totalPages: 5,
- },
- },
- },
- });
-
- wrapper.find('.next-page-item').trigger('click');
-
- expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ page: '2' });
- });
- });
-
describe('pipeline badge counts', () => {
it('should receive update-pipelines-count event', () => {
const element = document.createElement('div');
@@ -203,16 +209,18 @@ describe('Pipelines table in Commits and Merge requests', () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]);
createComponent({
- canRunPipeline: true,
- projectId: '5',
- mergeRequestId: 3,
+ props: {
+ canRunPipeline: true,
+ projectId: '5',
+ mergeRequestId: 3,
+ },
});
await waitForPromises();
});
describe('success', () => {
beforeEach(() => {
- jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
});
it('displays a toast message during pipeline creation', async () => {
await findRunPipelineBtn().trigger('click');
@@ -255,9 +263,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
`('displays permissions error message', async ({ status, message }) => {
const response = { response: { status } };
- jest
- .spyOn(Api, 'postMergeRequestPipeline')
- .mockImplementation(() => Promise.reject(response));
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockRejectedValue(response);
await findRunPipelineBtn().trigger('click');
@@ -281,14 +287,16 @@ describe('Pipelines table in Commits and Merge requests', () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]);
createComponent({
- projectId: '5',
- mergeRequestId: 3,
- canCreatePipelineInTargetProject: true,
- sourceProjectFullPath: 'test/parent-project',
- targetProjectFullPath: 'test/fork-project',
+ props: {
+ projectId: '5',
+ mergeRequestId: 3,
+ canCreatePipelineInTargetProject: true,
+ sourceProjectFullPath: 'test/parent-project',
+ targetProjectFullPath: 'test/fork-project',
+ },
});
- jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
await waitForPromises();
});
@@ -313,15 +321,15 @@ describe('Pipelines table in Commits and Merge requests', () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, []);
createComponent({
- projectId: '5',
- mergeRequestId: 3,
- canCreatePipelineInTargetProject: true,
- sourceProjectFullPath: 'test/parent-project',
- targetProjectFullPath: 'test/fork-project',
+ props: {
+ projectId: '5',
+ mergeRequestId: 3,
+ canCreatePipelineInTargetProject: true,
+ sourceProjectFullPath: 'test/parent-project',
+ targetProjectFullPath: 'test/fork-project',
+ },
});
- jest.spyOn(findModal().vm, 'show').mockReturnValue();
-
await waitForPromises();
});
@@ -331,7 +339,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
findRunPipelineBtn().trigger('click');
- expect(findModal().vm.show).toHaveBeenCalled();
+ expect(showMock).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/emoji/components/emoji_list_spec.js b/spec/frontend/emoji/components/emoji_list_spec.js
index a72ba614d9f..f6f6062f8e8 100644
--- a/spec/frontend/emoji/components/emoji_list_spec.js
+++ b/spec/frontend/emoji/components/emoji_list_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import EmojiList from '~/emoji/components/emoji_list.vue';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/emoji', () => ({
initEmojiMap: jest.fn(() => Promise.resolve()),
@@ -14,7 +14,8 @@ jest.mock('~/emoji', () => ({
}));
let wrapper;
-async function factory(render, propsData = { searchValue: '' }) {
+
+function factory(propsData = { searchValue: '' }) {
wrapper = extendedWrapper(
shallowMount(EmojiList, {
propsData,
@@ -23,35 +24,23 @@ async function factory(render, propsData = { searchValue: '' }) {
},
}),
);
-
- // Wait for categories to be set
- await nextTick();
-
- if (render) {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ render: true });
-
- // Wait for component to render
- await nextTick();
- }
}
const findDefaultSlot = () => wrapper.findByTestId('default-slot');
describe('Emoji list component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not render until render is set', async () => {
- await factory(false);
+ factory();
expect(findDefaultSlot().exists()).toBe(false);
+ await waitForPromises();
+ expect(findDefaultSlot().exists()).toBe(true);
});
it('renders with none filtered list', async () => {
- await factory(true);
+ factory();
+
+ await waitForPromises();
expect(JSON.parse(findDefaultSlot().text())).toEqual({
activity: {
@@ -63,7 +52,9 @@ describe('Emoji list component', () => {
});
it('renders filtered list of emojis', async () => {
- await factory(true, { searchValue: 'smile' });
+ factory({ searchValue: 'smile' });
+
+ await waitForPromises();
expect(JSON.parse(findDefaultSlot().text())).toEqual({
search: {
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
index 3aca346ff5f..7d6647fa6fd 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
@@ -6,7 +6,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
-import { IssuableType } from '~/issues/constants';
+import { TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarAssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
@@ -397,7 +397,7 @@ describe('Sidebar assignees widget', () => {
});
it('does not render invite members link on non-issue sidebar', async () => {
- createComponent({ props: { issuableType: IssuableType.MergeRequest } });
+ createComponent({ props: { issuableType: TYPE_MERGE_REQUEST } });
await waitForPromises();
expect(findInviteMembersLink().exists()).toBe(false);
});
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
index be0b14fa997..c43b8938ade 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
@@ -1,6 +1,6 @@
import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
const user = {
@@ -56,13 +56,13 @@ describe('Sidebar participant component', () => {
describe('when on merge request sidebar', () => {
it('when project member cannot merge', () => {
- createComponent({ issuableType: IssuableType.MergeRequest });
+ createComponent({ issuableType: TYPE_MERGE_REQUEST });
expect(findIcon().exists()).toBe(true);
});
it('when project member can merge', () => {
- createComponent({ issuableType: IssuableType.MergeRequest, canMerge: true });
+ createComponent({ issuableType: TYPE_MERGE_REQUEST, canMerge: true });
expect(findIcon().exists()).toBe(false);
});
diff --git a/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
index c3de076d6aa..169e2f37248 100644
--- a/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
+++ b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
@@ -3,7 +3,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarReferenceWidget from '~/sidebar/components/copy/sidebar_reference_widget.vue';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
@@ -52,7 +52,7 @@ describe('Sidebar Reference Widget', () => {
describe.each([
[TYPE_ISSUE, issueReferenceQuery],
- [IssuableType.MergeRequest, mergeRequestReferenceQuery],
+ [TYPE_MERGE_REQUEST, mergeRequestReferenceQuery],
])('when issuableType is %s', (issuableType, referenceQuery) => {
it('sets CopyableField `value` prop to reference value', async () => {
createComponent({
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
index fd8e72bac49..c5432eea3ff 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
-import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/sidebar/components/labels/labels_select_widget/dropdown_contents.vue';
import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue';
@@ -36,7 +36,7 @@ const errorQueryHandler = jest.fn().mockRejectedValue('Houston, we have a proble
const updateLabelsMutation = {
[TYPE_ISSUE]: updateIssueLabelsMutation,
- [IssuableType.MergeRequest]: updateMergeRequestLabelsMutation,
+ [TYPE_MERGE_REQUEST]: updateMergeRequestLabelsMutation,
[TYPE_EPIC]: updateEpicLabelsMutation,
[IssuableType.TestCase]: updateTestCaseLabelsMutation,
};
@@ -214,7 +214,7 @@ describe('LabelsSelectRoot', () => {
describe.each`
issuableType
${TYPE_ISSUE}
- ${IssuableType.MergeRequest}
+ ${TYPE_MERGE_REQUEST}
${TYPE_EPIC}
${IssuableType.TestCase}
`('when updating labels for $issuableType', ({ issuableType }) => {
diff --git a/spec/frontend/vue_shared/components/user_select_spec.js b/spec/frontend/vue_shared/components/user_select_spec.js
index b0e9584a15b..a2d5fefb63d 100644
--- a/spec/frontend/vue_shared/components/user_select_spec.js
+++ b/spec/frontend/vue_shared/components/user_select_spec.js
@@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import searchUsersQuery from '~/graphql_shared/queries/users_search.query.graphql';
import searchUsersQueryOnMR from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql';
-import { IssuableType } from '~/issues/constants';
+import { TYPE_MERGE_REQUEST } from '~/issues/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
import getIssueParticipantsQuery from '~/sidebar/queries/get_issue_participants.query.graphql';
@@ -409,7 +409,7 @@ describe('User select dropdown', () => {
describe('when on merge request sidebar', () => {
beforeEach(() => {
- createComponent({ props: { issuableType: IssuableType.MergeRequest, issuableId: 1 } });
+ createComponent({ props: { issuableType: TYPE_MERGE_REQUEST, issuableId: 1 } });
return waitForPromises();
});
diff --git a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb
index 4ccbf44af52..6826006949e 100644
--- a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb
+++ b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout, feature_category: :product_analytics do
let(:ce_temp_dir) { Dir.mktmpdir }
let(:ee_temp_dir) { Dir.mktmpdir }
- let(:timestamp) { Time.current.to_i }
+ let(:timestamp) { Time.now.utc.strftime('%Y%m%d%H%M%S') }
let(:generator_options) { { 'category' => 'Groups::EmailCampaignsController', 'action' => 'click' } }
before do
diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
index 088a901c80e..89c0ce46237 100644
--- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
@@ -210,31 +210,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Duration, feature_category: :continuous_int
end
end
- context 'when feature flag ci_use_downstream_pipeline_duration_for_calculation is disabled' do
- before do
- stub_feature_flags(ci_use_downstream_pipeline_duration_for_calculation: false)
- end
-
- it 'returns the duration of the running build' do
- travel_to(current) do
- expect(described_class.from_pipeline(pipeline)).to eq 1000.seconds
- end
- end
-
- context 'when there is no running build' do
- let!(:running_build) { nil }
-
- it 'returns the duration for builds and bridges' do
- travel_to(current) do
- # 260 = 160 (see above)
- # + success bridge build (60) + failed (60) + canceled (30)
- # - overlapping (success & failed) (20) - overlapping (failed & canceled) (30)
- expect(described_class.from_pipeline(pipeline)).to eq 260.seconds
- end
- end
- end
- end
-
# rubocop:disable RSpec/MultipleMemoizedHelpers
context 'when there are downstream bridge jobs' do
let_it_be(:success_direct_bridge) do
@@ -282,29 +257,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Duration, feature_category: :continuous_int
end
end
end
-
- context 'when feature flag ci_use_downstream_pipeline_duration_for_calculation is disabled' do
- before do
- stub_feature_flags(ci_use_downstream_pipeline_duration_for_calculation: false)
- end
-
- it 'returns the duration of the running build' do
- travel_to(current) do
- expect(described_class.from_pipeline(pipeline)).to eq 1000.seconds
- end
- end
-
- context 'when there is no running build' do
- let!(:running_build) { nil }
-
- it 'returns the duration for builds and bridges' do
- travel_to(current) do
- # 380 = 260 (see above) + success direct bridge (120)
- expect(described_class.from_pipeline(pipeline)).to eq 380.seconds
- end
- end
- end
- end
end
# rubocop:enable RSpec/MultipleMemoizedHelpers
end
diff --git a/spec/models/clusters/applications/crossplane_spec.rb b/spec/models/clusters/applications/crossplane_spec.rb
deleted file mode 100644
index d1abaa52c7f..00000000000
--- a/spec/models/clusters/applications/crossplane_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Clusters::Applications::Crossplane do
- let(:crossplane) { create(:clusters_applications_crossplane) }
-
- include_examples 'cluster application core specs', :clusters_applications_crossplane
- include_examples 'cluster application status specs', :clusters_applications_crossplane
- include_examples 'cluster application version specs', :clusters_applications_crossplane
- include_examples 'cluster application initial status specs'
-
- describe 'validations' do
- it { is_expected.to validate_presence_of(:stack) }
- end
-
- describe 'default values' do
- it { expect(subject.version).to eq(described_class::VERSION) }
- it { expect(subject.stack).to be_empty }
- end
-
- describe '#can_uninstall?' do
- subject { crossplane.can_uninstall? }
-
- it { is_expected.to be_truthy }
- end
-
- describe '#install_command' do
- let(:stack) { 'gcp' }
-
- subject { crossplane.install_command }
-
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
-
- it 'is initialized with crossplane arguments' do
- expect(subject.name).to eq('crossplane')
- expect(subject.chart).to eq('crossplane/crossplane')
- expect(subject.repository).to eq('https://charts.crossplane.io/alpha')
- expect(subject.version).to eq('0.4.1')
- expect(subject).to be_rbac
- end
-
- context 'application failed to install previously' do
- let(:crossplane) { create(:clusters_applications_crossplane, :errored, version: '0.0.1') }
-
- it 'is initialized with the locked version' do
- expect(subject.version).to eq('0.4.1')
- end
- end
- end
-
- describe '#files' do
- let(:application) { crossplane }
- let(:values) { subject[:'values.yaml'] }
-
- subject { application.files }
-
- it 'includes crossplane specific keys in the values.yaml file' do
- expect(values).to include('clusterStacks')
- end
- end
-end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 035f53db12e..eb0f3b3eaee 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -174,6 +174,23 @@ RSpec.describe API::Search, feature_category: :global_search do
end
end
+ context 'when there is a search error' do
+ let(:results) { instance_double('Gitlab::SearchResults', failed?: true, error: 'failed to parse query') }
+
+ before do
+ allow_next_instance_of(SearchService) do |service|
+ allow(service).to receive(:search_objects).and_return([])
+ allow(service).to receive(:search_results).and_return(results)
+ end
+ end
+
+ it 'returns 400 error' do
+ get api(endpoint, user), params: { scope: 'issues', search: 'expected to fail' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
context 'with correct params' do
context 'for projects scope' do
before do
diff --git a/spec/scripts/pipeline/create_test_failure_issues_spec.rb b/spec/scripts/pipeline/create_test_failure_issues_spec.rb
new file mode 100644
index 00000000000..fa27727542e
--- /dev/null
+++ b/spec/scripts/pipeline/create_test_failure_issues_spec.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+# rubocop:disable RSpec/VerifiedDoubles
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+require_relative '../../../scripts/pipeline/create_test_failure_issues'
+
+RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
+ describe CreateTestFailureIssue do
+ let(:env) do
+ {
+ 'CI_JOB_URL' => 'ci_job_url',
+ 'CI_PIPELINE_URL' => 'ci_pipeline_url'
+ }
+ end
+
+ let(:project) { 'group/project' }
+ let(:api_token) { 'api_token' }
+ let(:creator) { described_class.new(project: project, api_token: api_token) }
+ let(:test_name) { 'The test description' }
+ let(:test_file) { 'spec/path/to/file_spec.rb' }
+ let(:test_file_content) do
+ <<~CONTENT
+ # comment
+
+ RSpec.describe Foo, feature_category: :source_code_management do
+ end
+
+ CONTENT
+ end
+
+ let(:test_file_stub) { double(read: test_file_content) }
+ let(:failed_test) do
+ {
+ 'name' => test_name,
+ 'file' => test_file,
+ 'job_url' => 'job_url'
+ }
+ end
+
+ let(:categories_mapping) do
+ {
+ 'source_code_management' => {
+ 'group' => 'source_code',
+ 'label' => 'Category:Source Code Management'
+ }
+ }
+ end
+
+ let(:groups_mapping) do
+ {
+ 'source_code' => {
+ 'label' => 'group::source_code'
+ }
+ }
+ end
+
+ before do
+ stub_env(env)
+ end
+
+ describe '#find' do
+ let(:expected_payload) do
+ {
+ state: 'opened',
+ search: "#{failed_test['file']} - ID: #{Digest::SHA256.hexdigest(failed_test['name'])[0...12]}"
+ }
+ end
+
+ let(:find_issue_stub) { double('FindIssues') }
+ let(:issue_stub) { double(title: expected_payload[:title], web_url: 'issue_web_url') }
+
+ before do
+ allow(creator).to receive(:puts)
+ end
+
+ it 'calls FindIssues#execute(payload)' do
+ expect(FindIssues).to receive(:new).with(project: project, api_token: api_token).and_return(find_issue_stub)
+ expect(find_issue_stub).to receive(:execute).with(expected_payload).and_return([issue_stub])
+
+ creator.find(failed_test)
+ end
+
+ context 'when no issues are found' do
+ it 'calls FindIssues#execute(payload)' do
+ expect(FindIssues).to receive(:new).with(project: project, api_token: api_token).and_return(find_issue_stub)
+ expect(find_issue_stub).to receive(:execute).with(expected_payload).and_return([])
+
+ creator.find(failed_test)
+ end
+ end
+ end
+
+ describe '#create' do
+ let(:expected_description) do
+ <<~DESCRIPTION
+ ### Full description
+
+ `#{failed_test['name']}`
+
+ ### File path
+
+ `#{failed_test['file']}`
+
+ <!-- Don't add anything after the report list since it's updated automatically -->
+ ### Reports
+
+ - #{failed_test['job_url']} (#{env['CI_PIPELINE_URL']})
+ DESCRIPTION
+ end
+
+ let(:expected_payload) do
+ {
+ title: "#{failed_test['file']} - ID: #{Digest::SHA256.hexdigest(failed_test['name'])[0...12]}",
+ description: expected_description,
+ labels: described_class::DEFAULT_LABELS.map { |label| "wip-#{label}" } + [
+ "wip-#{categories_mapping['source_code_management']['label']}", "wip-#{groups_mapping['source_code']['label']}" # rubocop:disable Layout/LineLength
+ ]
+ }
+ end
+
+ let(:create_issue_stub) { double('CreateIssue') }
+ let(:issue_stub) { double(title: expected_payload[:title], web_url: 'issue_web_url') }
+
+ before do
+ allow(creator).to receive(:puts)
+ allow(File).to receive(:open).and_call_original
+ allow(File).to receive(:open).with(File.expand_path(File.join('..', '..', '..', test_file), __dir__))
+ .and_return(test_file_stub)
+ allow(creator).to receive(:categories_mapping).and_return(categories_mapping)
+ allow(creator).to receive(:groups_mapping).and_return(groups_mapping)
+ end
+
+ it 'calls CreateIssue#execute(payload)' do
+ expect(CreateIssue).to receive(:new).with(project: project, api_token: api_token).and_return(create_issue_stub)
+ expect(create_issue_stub).to receive(:execute).with(expected_payload).and_return(issue_stub)
+
+ creator.create(failed_test) # rubocop:disable Rails/SaveBang
+ end
+ end
+ end
+end
+# rubocop:enable RSpec/VerifiedDoubles
diff --git a/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
new file mode 100644
index 00000000000..0b0c9edcb42
--- /dev/null
+++ b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'variable list pagination' do |variable_type|
+ first_page_count = 20
+
+ before do
+ first_page_count.times do |i|
+ case variable_type
+ when :ci_variable
+ create(variable_type, key: "test_key_#{i}", value: 'test_value', masked: true, project: project)
+ when :ci_group_variable
+ create(variable_type, key: "test_key_#{i}", value: 'test_value', masked: true, group: group)
+ else
+ create(variable_type, key: "test_key_#{i}", value: 'test_value', masked: true)
+ end
+ end
+
+ visit page_path
+ wait_for_requests
+ end
+
+ it 'can navigate between pages' do
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(page.all('.js-ci-variable-row').length).to be(first_page_count)
+ end
+
+ click_button 'Next'
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(page.all('.js-ci-variable-row').length).to be(1)
+ end
+
+ click_button 'Previous'
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(page.all('.js-ci-variable-row').length).to be(first_page_count)
+ end
+ end
+
+ it 'sorts variables alphabetically in ASC and DESC order' do
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
+ expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_8')
+ end
+
+ click_button 'Next'
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9')
+ end
+
+ page.within('[data-testid="ci-variable-table"]') do
+ find('.b-table-sort-icon-left').click
+ end
+
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9')
+ expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_0')
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 8cf40f60ee6..80d9713aa2f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2035,182 +2035,182 @@
dom-accessibility-api "^0.5.1"
pretty-format "^26.4.2"
-"@tiptap/core@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.218.tgz#010f82931586a2c1e3ad3c123cf96f39b099d8c3"
- integrity sha512-RCrT4AYgH+2mQtt26EkFjIkGZGsuT7vKTbknEhpXdhWgbxy/fg65WyPs8hQMyocjOqPX2vbike4PfqWi9nBqCw==
-
-"@tiptap/extension-blockquote@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.218.tgz#1dfd1afa45ffce93b90fd9c4786c87c9f64e9759"
- integrity sha512-cQ1mjWjSqe6ztglfRRBIAzZ+6Ro5jN7QpSstVMravqW3lu52omXqYx5SfDNycoO6574BoSw2Wijz36RwQLeAsQ==
-
-"@tiptap/extension-bold@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.218.tgz#9fdd5de1be8fd44f8055f4d18c3714fd7a69cf9e"
- integrity sha512-XKQ9QiXspGLY9LyZVhPTQFfJQEDzrwCigILG+QwM3WAp/hKNDJsUy4GXRl/Rdtx+5mfqGyh+Zlo41xF2WhoKUg==
-
-"@tiptap/extension-bubble-menu@2.0.0-beta.218", "@tiptap/extension-bubble-menu@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.218.tgz#e4524a90797e552c39c14717dbc297b1d4bd7538"
- integrity sha512-NXVaRSoweM54AGCJCJWkY3D9CfYlARtC04+7T8EF0Dwz99RcVzDHCXaNJGk+rMH7vGxsJop4RxHEQO/D2pgADw==
+"@tiptap/core@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.220.tgz#ced4b8f13ad6361f957275510bd0c005de29d18c"
+ integrity sha512-F2Q666xJqijBU5o+GqekqseNgIEMTs6BhsLDaf9DwThhljGLS8RXKnSvQxrxLNrYEPpw39n/G3Qt8YAOk5qR6w==
+
+"@tiptap/extension-blockquote@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.220.tgz#acce6a7d2fda829296e1e0b6386f618ea8ae328e"
+ integrity sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==
+
+"@tiptap/extension-bold@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.220.tgz#f10468317fd5c63ebab68be907e33fb138a60ef9"
+ integrity sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==
+
+"@tiptap/extension-bubble-menu@2.0.0-beta.220", "@tiptap/extension-bubble-menu@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.220.tgz#3fea0c846f73a237f562fdce05671ef1fa025943"
+ integrity sha512-wthyec7s0vZlTSEAAZEgoFfx/1Arwg1zxDUrrE+YAost/Yn+w4xQksz/ts5Bx90iOk2qsJ+jzzttLRV17Ku7lA==
dependencies:
lodash "^4.17.21"
tippy.js "^6.3.7"
-"@tiptap/extension-bullet-list@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.218.tgz#be695a919df1354d3b212ec9d41a3997e13ff283"
- integrity sha512-pf4MMcM65tYLauogxfIsvmFf5pqV9hlTAsgsQhPw2L3ayceOr6JvmiGHBVlZifhNcIuSDY/3i2Ft/O4tKFncbQ==
-
-"@tiptap/extension-code-block-lowlight@2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.218.tgz#b08f80e2142474ba9d31b312f0614c9e487ecb77"
- integrity sha512-6bFESBajfbLM2vaA9aAE9+FBxcpGj72l2ECavd2COflUX5Gjd+agEzSZtbln6UQsdhcn1FaiU4T6t6QPD8CKBg==
-
-"@tiptap/extension-code-block@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.218.tgz#859eebd43bf85d0f94693e174657f5eb71d34f68"
- integrity sha512-ajmHrZ2xDgCjLaokvmdiI7fETqRyvI/qHD4jzmzMTKdIiwLzwCgTVmbU73TmZSdR1+fO+pyDTp48YIxDy9zRdA==
-
-"@tiptap/extension-code@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.218.tgz#1aa8b4c064343463f30375e48c48e81294188533"
- integrity sha512-i/gJuY78+QWTc4LeSHM613IxKCC63oqGKounl+xlBqtH6b/w9Rx8R6WJQIz78Mth5dQHKTSUPwjR5+UcmKhFRA==
-
-"@tiptap/extension-document@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.218.tgz#34e76a345759faedcde56e95f2ab71f4437f4eb2"
- integrity sha512-bpNhUEho8sbjLVYGX+jkqZv//2OVFs0RhiNtMJ1SUbdr9LDJu+KMo86KUkTkRPPgoJPDkEViIgNrYcc66MFxmQ==
-
-"@tiptap/extension-dropcursor@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.218.tgz#c558236792647393cd351ad18394c4e2d3853e20"
- integrity sha512-RvYB0koNJs3ETcVnzwda0hhDxCa2hoKW1C3WiRKg1+pYA5oW12NtiNWqViQKkSn1ugQXXzhdxMHGCSr4BczmAA==
-
-"@tiptap/extension-floating-menu@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.218.tgz#583ff6f10663059c94cc59c417ea7e290b1ebc45"
- integrity sha512-OzmucdEJOK7Geq6C3oBZg1m1lBrAAm6SyMPn+0fN83ijixYJAChi7c20A+mrR6aeTlmjASi2CXFkrrVmNV7ZDQ==
+"@tiptap/extension-bullet-list@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.220.tgz#ffc04992bbee53bc858aab6c082f17419a2236b7"
+ integrity sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==
+
+"@tiptap/extension-code-block-lowlight@2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.220.tgz#9496a1989b5385872456f6910a43ac822b4896c1"
+ integrity sha512-xMwbl5O50yaIGYQF3yrBM7Ft0JYejkuEo161jRElYG+PYvUCvbf2wB5oLNtRknj00WOM01kPXH2xMTuk8fTOPg==
+
+"@tiptap/extension-code-block@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.220.tgz#8396b72f634d77d23b9ea01c9a253e8a7f471471"
+ integrity sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==
+
+"@tiptap/extension-code@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.220.tgz#3543afeda2b0b240682a36eeb401b00a3da56ab6"
+ integrity sha512-JKKDZoceagqVXeC1XF/gOkKhLtsbYJYV+MRDorLnQVz4tXcg/SMs5Ez7OM9MxSSior8fIbUFMNsj1/UNlG+tFw==
+
+"@tiptap/extension-document@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.220.tgz#15b4db7a92659eff7efc6d4d877dcf72e3fd61b6"
+ integrity sha512-2sja4ZvOb4iynHrzinnclCSFgLyo6fJc1fBV5fIYaOgZOYcvz9KK8fgKiq+wIpG58sJEmQ5kcwwBlkXv+NTK+g==
+
+"@tiptap/extension-dropcursor@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.220.tgz#b635fa6cdf9be1027579c7ab6c00e5a811b3b30b"
+ integrity sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==
+
+"@tiptap/extension-floating-menu@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.220.tgz#35eb154227533ada738c922be2f8cf18426fe4bf"
+ integrity sha512-+WfcBEedm82ntaVIEQAGz0Om96Rpav7a+4f7e8N4PrLKm6nZ3gBaEkZVQ6vjJ6S/1htiWCv1XosYIwRboPBG0w==
dependencies:
tippy.js "^6.3.7"
-"@tiptap/extension-gapcursor@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.218.tgz#e415cdffb58dd2af47c72267ddf53d1042a4f784"
- integrity sha512-buawfpYb+wgXj+01qLb58Cv9x6dhcM4oHoFggBRo8E3mj561plfDKWK45OZIbyqRQeE8U3bbwfSYgail5wd7JA==
-
-"@tiptap/extension-hard-break@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.218.tgz#8fe7fd2cdb21f2e053722415a29bc27db3cd6671"
- integrity sha512-mAtjRjkEQ2I+WGTOn90SefRvpF/HVVLRji9jidPTCnTVOJzEYax5xPlr5vFh2N4wcprWWi/T5qcwybs1oV3uig==
-
-"@tiptap/extension-heading@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.218.tgz#941e28aaaaaaa1c0b41feac62eb3de124a8cc9a3"
- integrity sha512-JYkDLk19wFdjHG1prUfNV6EzZuDt9P5psaPaiwGoAujUMHKBJTllSC+UZ4H7YL87RpUIPs5GHQMWL91HP+0RYQ==
-
-"@tiptap/extension-highlight@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.0.0-beta.218.tgz#f2cdb44c2195886e3ddbda964deb82cb723bd640"
- integrity sha512-/flgMoCY26u6jsKc6NVlb/DXo6AV4ustMIiQCE9kJxetYSkWCdaM24RUn4iFGq7F5KouovDLq3geO2XVnzE+6g==
-
-"@tiptap/extension-history@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.218.tgz#d13a9030012081cd0d784074526d1d9411c7302a"
- integrity sha512-4xfEKWGHU2785vUXXaIguxIaKKQ3b+z7463QhF2qhcpm5Ip6eupYbbE0jy8S12wSujhrjk88W0Ud6n3puV73xA==
-
-"@tiptap/extension-horizontal-rule@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.218.tgz#8a8c7ce49cbb0ddb8ef049a1ecad0e37a95da523"
- integrity sha512-JlKMzqgheujKkx0ZQvUCO+WkIUQQKkWMiUSEsTaAvgIfzI97dKuP/r7CsgZczurtJ9edRPmQqMRwxZ4XLtC/aQ==
-
-"@tiptap/extension-image@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.218.tgz#c03af3356b39c2913704746bf24ad4e28309f96f"
- integrity sha512-WefQYI04ZzbAX2x3z6fpk8bjWmR6nW7p+EUN6v9yeBiKUAeMClCL1toAhQcKBI0yBinTz+OOjlnc3iKa/nOMzw==
-
-"@tiptap/extension-italic@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.218.tgz#9dac0e94b481df04eca409dbe6cf7e7165ab9b7e"
- integrity sha512-+/NRGoLQXi+CCziVT7MixB10jC72c5BCG6w15H4hfHkD+eJc/DfzLCuIaePzYCZC/xqjNI/EBNh6BP3HTMHCpQ==
-
-"@tiptap/extension-link@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.218.tgz#40d99cebfe9cbdb9c39b8cdb61dc8ce663b2bada"
- integrity sha512-Vi+qZOzqS+tTpK2q23q5wLEDxIZMiRNHduzJtZh2wKryLPjbs7jEnxSVQybvQqRHAhUC6uGsh+9nhN0+fq6oAw==
- dependencies:
- linkifyjs "^3.0.5"
-
-"@tiptap/extension-list-item@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.218.tgz#21ae2c21da9488b05be4e03dc14e3ec9c7fdbf90"
- integrity sha512-ZqSA0dZAzACYgfFAM+RWCRhMPUcZiWT8JhpN/2on0egI+b1gtt2RAA4niBaNFp4skGQS8a4bCy5cttIHUNO7FA==
-
-"@tiptap/extension-ordered-list@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.218.tgz#7274adcd088be457db40126a3fb898b32cc1a085"
- integrity sha512-Oe/Kl6+mNSBOpF4fXeQxFXu7/fMFPwn3UZ2hR+DTIJkoLbviKehcxSInqWmUV0n5FbEr3usErPDYDDrCH+zEVw==
-
-"@tiptap/extension-paragraph@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.218.tgz#7591c886f6937e650a0d767a51c2f6fcc53c53f8"
- integrity sha512-qBNGJl0r/uufPoC02fqdjRQBmafE3zd+uPevtN+Nozze4kBc5VIlkLMw6VR83jMMeWKNhbQ97aCCYQYjCabaDw==
-
-"@tiptap/extension-strike@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.218.tgz#781460fa23a7273617d765ec0c9c737cfaf8faa9"
- integrity sha512-PZ3FGmh3XkdQqRIfMZrHApFVerf/2vQu0V1IWTxUDpaUSD/TL5A1dp3R/O/dlVauhKV3zDs/XGfepXJ99fyXAg==
-
-"@tiptap/extension-subscript@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.218.tgz#e5be3c8555e161eb950a8de1494b925822b4c4c2"
- integrity sha512-vbYYY4vPc5/waqsRNKEliske9kib2PkjLAbBjhqhGoVYfoKHji2ogxBrAE0x9ioElOdjse9oQrBp10YUOAXMRA==
-
-"@tiptap/extension-superscript@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.218.tgz#b640ace883bb82fc95d76ef8f7e0fee176e04879"
- integrity sha512-r+jAo1TwFacX1AB+ds4ptFfQesP+Uy+6Kt+pxGrQnWlDL9+YjXKfKwEIpJIuk5GiEvr0ZMbk+qDV2DVEaP06xg==
-
-"@tiptap/extension-table-cell@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.218.tgz#7308f31608c19222a101c98a263d620cb334c767"
- integrity sha512-SFnBHxYifDCmHnSDqDS+LBKxTeujonE+7OeM7/LV+1Qqp44FKZsleQVGUp5fUmP7LmdRZSeoWzKQNl1SC4dhuA==
-
-"@tiptap/extension-table-header@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.218.tgz#52cddb16102a26acc2b18daa867575249e5cae2b"
- integrity sha512-LZd10qo1oHH4GESlsnPgagO0INicewAdJLBV4PdXIKsEOCyYhVP6IF3WPSsafxRqWasmiJ0jg1yGDhXx9Gqy9A==
-
-"@tiptap/extension-table-row@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.218.tgz#a643c1793d238bdcfc0161e0ccea0994c338735d"
- integrity sha512-WSljowOBl4eVnl3ihxQzlBq1ff4NUvzMVI43OvuPQkDLMzAnkuNEwFSBt8csolc4gDsJHvMFbK9wgftIMbXgdA==
-
-"@tiptap/extension-table@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.218.tgz#2ed0b81620aea321b6302934847f7d00500ffa91"
- integrity sha512-Beyzkso7twfxfdkNJ5WY+FC3fqddkOseDKcjKU0YQVg/81EvlFEFRiyLN6S5b8XHhDXuNzV49pYo9hIbQ75iwA==
-
-"@tiptap/extension-task-item@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.218.tgz#8a1168cd1f26b7a1b2dc1a6e45461ebfdb431450"
- integrity sha512-RSFeKEpqDb+e0v0eBT9EM2VZW3GT74LQoT+UwZP6LM8fPIYS/wHWHDvPRo0zl3JpA04x13UbS6lOZ2MJruykzQ==
-
-"@tiptap/extension-task-list@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.218.tgz#10e1eb25cea8fbcb448329f5f529a7e572705f2b"
- integrity sha512-MscfmeeAKsRrnyFDcUoByikfQihDQhSSYYXG0AgvcQu3Qjf/eoVp44eAIQlCsh67xOeDDdGI2ripmeqJqUFZUQ==
-
-"@tiptap/extension-text@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.218.tgz#087641b1b4d8af41363a1e33fd6c4b52fa2c689a"
- integrity sha512-Z14dRRIjQwPGJD3+joD3nEs6NcQUORuPXa2BDWsFcAb6R+yPz7j9Wpn4nkcWCw2CIqnMm17ERs+KSSvKvzWY6Q==
-
-"@tiptap/pm@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.0-beta.218.tgz#4b737f15444847ff4d60bc8f9d0d23dad432afe9"
- integrity sha512-mRKHch3BMleLmFJbQcD7Su7oFayHBCl5zzYlQ0ws44dLCR7zlGL85Fxxfk+8wd64dwDjlbdAAZmRLQctq4JgjA==
+"@tiptap/extension-gapcursor@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.220.tgz#07c96f7adc354d19b6209ea1e080188fb8d63de5"
+ integrity sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==
+
+"@tiptap/extension-hard-break@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.220.tgz#8ff432615d9c9090c3d59c2a745c88e4f39ab1a3"
+ integrity sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==
+
+"@tiptap/extension-heading@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.220.tgz#b4889de7b3f152ff88a119d6cb6a22537eff73a2"
+ integrity sha512-7mrHRj++UaZ26C2Gjwb0WKWAzpiKb8TOYkVC2uMaCwaNhLDXpFEwZ7RtJRSTNBHkIGnMO46BH8Z0qlkFMmk9Jw==
+
+"@tiptap/extension-highlight@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.0.0-beta.220.tgz#1bf2954524b99bb393dad46b5613b84aa660713f"
+ integrity sha512-+h4seFq99b0dCmShVlSc44PBQUiW4xBXze61V6ZNILLkfzo27wrj0W+I3WrdSXX9uz3wwE/BR+3T8m1Ro8lHng==
+
+"@tiptap/extension-history@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.220.tgz#6370b28872b29288d655cd14211efb8dc76daba0"
+ integrity sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==
+
+"@tiptap/extension-horizontal-rule@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.220.tgz#4b8eaf081b38359235312308ebd59950705c7b10"
+ integrity sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==
+
+"@tiptap/extension-image@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.220.tgz#c197b0dbd2f5d7a08f91e63cb8ca98c1972159a4"
+ integrity sha512-xyzlY/cupj/7AVqybQDaPaJ3SwKqe12xMWQlWxhhksuNpbQ6RGHrJz0DBSe61kIkaTZmIUBw055IFEMOPFF53g==
+
+"@tiptap/extension-italic@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.220.tgz#94e442689f69e694a2a983eabcae0ccc803262b9"
+ integrity sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==
+
+"@tiptap/extension-link@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.220.tgz#c9954613cd1e0a0f1527853b732ef50dff734eac"
+ integrity sha512-vjEA8cE37ZZVVgPHSpttw3kbJoClb+ya/BVukDtJ1h6C7mIR1rqzNxTgpbnXJuA8xww0JOjpa5dpzEgcs294fA==
+ dependencies:
+ linkifyjs "^4.1.0"
+
+"@tiptap/extension-list-item@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz#c2fcff1fb9148d303d78b0336032a6353a86ff6c"
+ integrity sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==
+
+"@tiptap/extension-ordered-list@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.220.tgz#1fac8e8c2f8c0187e23ede59764fd031d5d1a83a"
+ integrity sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==
+
+"@tiptap/extension-paragraph@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz#d552dfdeeab9856e9eb8f0a7cf850f37d7cced69"
+ integrity sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==
+
+"@tiptap/extension-strike@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz#2beb02d2d8807056ff3ea4ea74d9f6abba42bf78"
+ integrity sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==
+
+"@tiptap/extension-subscript@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.220.tgz#3b9ebd181804f411f7755b8a73d1863dc72c5b8a"
+ integrity sha512-+C6nyAU4aaeCMvtBI1CJrMseE+YYqLUmmUVOK4ka3ZjmYkn1n+Tduf0ZGQHYmSSMDHPqQ8KsN+AQwaeSWKM/dA==
+
+"@tiptap/extension-superscript@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.220.tgz#93289e25bce0fb13608d7d41e74a13a3faf4d3c6"
+ integrity sha512-h7Qh8Jqb5r84hS0GhhQdNPFk+6AZhvbOKv/4dP6g9S5mRc287WlfhTrbpMdHI/p0r5vKkpLmAXpNCn6IImd3jQ==
+
+"@tiptap/extension-table-cell@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.220.tgz#b3ae21a72a2012cb5e2656d91c90b7424672e6f8"
+ integrity sha512-JvX9CTaDBBbI1Qra7pwhsv0vD6Y3A+X6PL7EYVrqIHZlmWq7Lz2ELxjx8RkWyp2LzowVNgZwUu2i3yHakaX5oA==
+
+"@tiptap/extension-table-header@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.220.tgz#3f6050847bc978dbcb117a9fa7dd16e13dd3b633"
+ integrity sha512-oOCBxrOuHCy4feuZKcBU9WWxi2SqBwfn/rmzSU6loKK8rR1+0olyAYu8IREb6DMmemTxl0ITp74hBxKeZyzjrA==
+
+"@tiptap/extension-table-row@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.220.tgz#5339c2be44cbbf871768a809a1f3694bef2031e0"
+ integrity sha512-DbYfrzLREulL+xOx74XAuhuqHUNi0t9hXDzG6RYdPiNnMhX/HhmTIV7bLNjEGxy6rOX0LhDzrBpNlA1elYUrwQ==
+
+"@tiptap/extension-table@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.220.tgz#42731e23e98d2c074e4e0460525718e57ebfc6a9"
+ integrity sha512-wdA957lSwIPtaSEAGw/KDXvhKAv28XkooHctY8FxqxEtvyMyCA8v0YXuOhGny/Uz6VZE+vdRiESMjwRU4ZQQ4g==
+
+"@tiptap/extension-task-item@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.220.tgz#4c83d7b55587da9c8ed51cca6cd0edee13ff188d"
+ integrity sha512-dta4V3GkL3C+gYUUkv26gxvCD11JYE7XYp4GSED/1X/3aHOdV9HcYRtIVnHqb4YwfuX/AJyIDfjhxc2tNGevkQ==
+
+"@tiptap/extension-task-list@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.220.tgz#ba349d84dfb9fd5dff90bfea8fb234cd90383d78"
+ integrity sha512-Hix7/Er4T4xKz4uLTxniJaDtcctmooaxoHiHv4yDUOXZYiK5BZypr8cbCcUaoD3qpfGe8O5JBzY2sbwk0PkNwA==
+
+"@tiptap/extension-text@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.220.tgz#3f51d4aac11c16d79cf8ca22502898b67f5bc2f5"
+ integrity sha512-3tnffc2YMjNyv7Lbad6fx9wYDE/Buz8vhx76M2AOSrjYbzmTJf7mLkgdlPM0VTy7FGZD5CGgHJAgYNt5HIqPkQ==
+
+"@tiptap/pm@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.0-beta.220.tgz#04e4c98e4d042ea8d67148ec6676f7078c6bac5a"
+ integrity sha512-O9mGcmwUpEr630HY9RylIyZJKnpXi3xWINWNiAEfRJ1br5j5pHRoVRJQ1HzU+6+Z+i/8qp3zRHGLTBqihaZETA==
dependencies:
prosemirror-changeset "^2.2.0"
prosemirror-collab "^1.3.0"
@@ -2231,18 +2231,18 @@
prosemirror-transform "^1.7.0"
prosemirror-view "^1.28.2"
-"@tiptap/suggestion@^2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.218.tgz#c19b88f61f93e9caf9d669a5444b5fc066082fa0"
- integrity sha512-wuySgX9mBjr15dGjFZlAIEHC+fs4MiAXJXywEcFUYl+hFDhCejyeKCl/Bw4Xf7JZiiUtfE0REeL5DkPvPmlFwQ==
+"@tiptap/suggestion@^2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.220.tgz#2dc05f65e89006ffaad9f2b6a3468311a305e5ee"
+ integrity sha512-lYb2HOAKJLjEBbTx5VXA32wRryQiMwaKkNfr3v6UhlwoNgD6NkCYID08UJbpMV7iM+iFQp9408D/vVWFwvOuKg==
-"@tiptap/vue-2@2.0.0-beta.218":
- version "2.0.0-beta.218"
- resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.218.tgz#469bff8ca18b028a14d58a6dcf1b28f0680be120"
- integrity sha512-sbSSzNVCt/NS3OCRZdo8APhxty0MeFqeraHgmnhPnYbOei9oQJXrz9OyR6EhqQTAJXsaV7qX+LrYbThxOKtKPA==
+"@tiptap/vue-2@2.0.0-beta.220":
+ version "2.0.0-beta.220"
+ resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.220.tgz#a3d9d84fc3cb6f1130bcd7e23fab9a4c56034312"
+ integrity sha512-GGK2M/pBVZSh2E0y1JXWVW7vllKvc2b/AwqPFZmbOGLKmxbH/xeaIeqOvt2w8b8RiA3G7UOq2lUGyjhn0/PnoQ==
dependencies:
- "@tiptap/extension-bubble-menu" "^2.0.0-beta.218"
- "@tiptap/extension-floating-menu" "^2.0.0-beta.218"
+ "@tiptap/extension-bubble-menu" "^2.0.0-beta.220"
+ "@tiptap/extension-floating-menu" "^2.0.0-beta.220"
"@tootallnate/once@2":
version "2.0.0"
@@ -8283,10 +8283,10 @@ linkify-it@^4.0.1:
dependencies:
uc.micro "^1.0.1"
-linkifyjs@^3.0.5:
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-3.0.5.tgz#99e51a3a0c0e232fcb63ebb89eea3ff923378f34"
- integrity sha512-1Y9XQH65eQKA9p2xtk+zxvnTeQBG7rdAXSkUG97DmuI/Xhji9uaUzaWxRj6rf9YC0v8KKHkxav7tnLX82Sz5Fg==
+linkifyjs@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.0.tgz#0460bfcc37d3348fa80e078d92e7bbc82588db15"
+ integrity sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==
loader-runner@^2.4.0:
version "2.4.0"