diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-01 21:13:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-01 21:13:05 +0000 |
commit | 58acbd41a1ee5aa51777f2ef88ce03bd698530c7 (patch) | |
tree | 3dc36a5296cf53123f494a49892cbb8267d31907 | |
parent | ad1e76fb4d1392c890c8b5e218a256a416d5a50b (diff) | |
download | gitlab-ce-58acbd41a1ee5aa51777f2ef88ce03bd698530c7.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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" |