diff options
125 files changed, 1445 insertions, 736 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbf8265da85..936b9410a60 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ variables: GIT_SUBMODULE_STRATEGY: "none" GET_SOURCES_ATTEMPTS: "3" DEBIAN_VERSION: "bullseye" - CHROME_VERSION: "103" + CHROME_VERSION: "106" DOCKER_VERSION: "20.10.14" RUBY_VERSION: "2.7" GO_VERSION: "1.18" diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 3c9a4b09b9b..2bb47c77ba5 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -2,7 +2,7 @@ # project here: https://gitlab.com/gitlab-org/gitlab/-/project_members # As described in https://docs.gitlab.com/ee/user/project/code_owners.html -* @gitlab-org/maintainers/rails-backend @gitlab-org/maintainers/frontend @gitlab-org/maintainers/database @gl-quality/qe-maintainers @gitlab-org/delivery @gitlab-org/maintainers/cicd-templates @nolith @jacobvosmaer-gitlab @gitlab-org/tw-leadership +* @gitlab-org/maintainers/rails-backend @gitlab-org/maintainers/frontend @gitlab-org/maintainers/database @gl-quality/qe-maintainers @gl-quality/tooling-maintainers @gitlab-org/delivery @gitlab-org/maintainers/cicd-templates @nolith @jacobvosmaer-gitlab @gitlab-org/tw-leadership CODEOWNERS @gitlab-org/development-leaders @gitlab-org/tw-leadership docs/CODEOWNERS @gitlab-org/development-leaders @gitlab-org/tw-leadership diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 0dbe58d1e10..4d120de277a 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -88,17 +88,6 @@ update-assets-compile-test-cache: - echo -n "${GITLAB_ASSETS_HASH}" > "cached-assets-hash.txt" artifacts: {} # This job's purpose is only to update the cache. -# TODO: Remove this as it's duplicating update-assets-compile-*-cache -update-yarn-cache: - extends: - - .default-retry - - .default-utils-before_script - - .yarn-cache-push - - .shared:rules:update-cache - stage: prepare - script: - - yarn_install_script - update-storybook-yarn-cache: extends: - .default-retry diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 2c75c9a5a0f..d461183a2ca 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -219,23 +219,16 @@ - *node-modules-cache - *assets-tmp-cache -# TODO: Remove this as it's duplicating .assets-compile-cache-push -.yarn-cache-push: - cache: - - *node-modules-cache-push - .assets-compile-cache: cache: - *ruby-gems-cache - *node-modules-cache - - *assets-cache - *assets-tmp-cache .assets-compile-cache-push: cache: - *ruby-gems-cache # We don't push this cache as it's already rebuilt by `update-setup-test-env-cache` - *node-modules-cache-push - - *assets-cache-push - *assets-tmp-cache-push .storybook-yarn-cache: @@ -245,7 +238,7 @@ .storybook-yarn-cache-push: cache: - - *node-modules-cache # We don't push this cache as it's already rebuilt by `update-yarn-cache` + - *node-modules-cache # We don't push this cache as it's already rebuilt by `update-assets-compile-*-cache` - *storybook-node-modules-cache-push .use-pg11: diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml index c631161aa08..69ce028987a 100644 --- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml @@ -25,7 +25,7 @@ include: - cd qa && bundle install .review-qa-base: - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.33-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 extends: - .use-docker-in-docker - .bundle-base diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index db0ceae2868..41d61f16d4c 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -856,6 +856,8 @@ .frontend:rules:minimal-default-rules: rules: + - <<: *if-merge-request-approved + when: never - <<: *if-automated-merge-request when: never - <<: *if-security-merge-request @@ -930,14 +932,16 @@ - <<: *if-merge-request-labels-run-all-jest - <<: *if-merge-request-labels-frontend-and-feature-flag - <<: *if-merge-request - changes: ["{package.json,yarn.lock}"] + changes: *frontend-dependency-patterns - <<: *if-merge-request changes: [".gitlab/ci/rules.gitlab-ci.yml", ".gitlab/ci/frontend.gitlab-ci.yml"] - <<: *if-automated-merge-request changes: *code-backstage-patterns - <<: *if-security-merge-request changes: *code-backstage-patterns - - <<: *if-default-branch-refs + - <<: *if-merge-request-not-approved + when: never + - <<: *if-default-refs changes: *code-backstage-patterns .frontend:rules:jest:minimal: @@ -950,7 +954,7 @@ - <<: *if-merge-request-labels-frontend-and-feature-flag when: never - <<: *if-merge-request - changes: ["{package.json,yarn.lock}"] + changes: *frontend-dependency-patterns when: never - <<: *if-merge-request changes: [".gitlab/ci/rules.gitlab-ci.yml", ".gitlab/ci/frontend.gitlab-ci.yml"] @@ -964,18 +968,24 @@ - <<: *if-merge-request-labels-as-if-foss - <<: *if-merge-request-labels-run-all-jest - <<: *if-merge-request - changes: ["{package.json,yarn.lock}"] + changes: *frontend-dependency-patterns - <<: *if-security-merge-request changes: *code-backstage-patterns + - <<: *if-merge-request-not-approved + when: never + - <<: *if-merge-request + changes: *frontend-patterns-for-as-if-foss .frontend:rules:jest:minimal:as-if-foss: rules: - !reference [".strict-ee-only-rules", rules] - !reference [".frontend:rules:minimal-default-rules", rules] + - <<: *if-merge-request-labels-as-if-foss + when: never - <<: *if-merge-request-labels-run-all-jest when: never - <<: *if-merge-request - changes: ["{package.json,yarn.lock}"] + changes: *frontend-dependency-patterns when: never - <<: *if-fork-merge-request when: never diff --git a/.rubocop_todo/rails/lexically_scoped_action_filter.yml b/.rubocop_todo/rails/lexically_scoped_action_filter.yml index 9edc8f7ce58..dde0da13d3c 100644 --- a/.rubocop_todo/rails/lexically_scoped_action_filter.yml +++ b/.rubocop_todo/rails/lexically_scoped_action_filter.yml @@ -1,15 +1,13 @@ --- Rails/LexicallyScopedActionFilter: - # Offense count: 73 - # Temporarily disabled due to too many offenses - Enabled: false + Details: grace period Exclude: - 'app/controllers/admin/groups_controller.rb' + - 'app/controllers/admin/hooks_controller.rb' - 'app/controllers/clusters/base_controller.rb' - 'app/controllers/clusters/clusters_controller.rb' - 'app/controllers/concerns/enforces_two_factor_authentication.rb' - 'app/controllers/concerns/integrations/actions.rb' - - 'app/controllers/concerns/multiple_boards_actions.rb' - 'app/controllers/concerns/oauth_applications.rb' - 'app/controllers/concerns/spammable_actions/captcha_check/html_format_actions_support.rb' - 'app/controllers/confirmations_controller.rb' @@ -17,14 +15,17 @@ Rails/LexicallyScopedActionFilter: - 'app/controllers/groups/group_members_controller.rb' - 'app/controllers/groups/milestones_controller.rb' - 'app/controllers/groups/runners_controller.rb' + - 'app/controllers/groups/settings/repository_controller.rb' - 'app/controllers/groups/uploads_controller.rb' - 'app/controllers/groups_controller.rb' - 'app/controllers/import/base_controller.rb' - 'app/controllers/oauth/applications_controller.rb' - 'app/controllers/passwords_controller.rb' + - 'app/controllers/projects/analytics/cycle_analytics/stages_controller.rb' - 'app/controllers/projects/badges_controller.rb' - 'app/controllers/projects/branches_controller.rb' - 'app/controllers/projects/environments_controller.rb' + - 'app/controllers/projects/hooks_controller.rb' - 'app/controllers/projects/incidents_controller.rb' - 'app/controllers/projects/issue_links_controller.rb' - 'app/controllers/projects/issues_controller.rb' @@ -36,6 +37,8 @@ Rails/LexicallyScopedActionFilter: - 'app/controllers/projects/project_members_controller.rb' - 'app/controllers/projects/prometheus/alerts_controller.rb' - 'app/controllers/projects/releases_controller.rb' + - 'app/controllers/projects/settings/integration_hook_logs_controller.rb' + - 'app/controllers/projects/settings/merge_requests_controller.rb' - 'app/controllers/projects/snippets_controller.rb' - 'app/controllers/projects/tags_controller.rb' - 'app/controllers/projects/todos_controller.rb' diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue index 449b9d31dcb..346a038000e 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue @@ -297,6 +297,7 @@ export default { data-testid="pipeline-form-ci-variable-value" data-qa-selector="ci_variable_value_field" class="gl-font-monospace!" + spellcheck="false" /> </gl-form-group> diff --git a/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue index f49cd476cb2..fa90e0e3e6c 100644 --- a/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue +++ b/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue @@ -275,6 +275,7 @@ export default { data-testid="pipeline-form-ci-variable-value" data-qa-selector="ci_variable_value_field" class="gl-font-monospace!" + spellcheck="false" /> </gl-form-group> diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index bc49464a560..7bc75127876 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -471,8 +471,14 @@ export default { }, fetchData(toggleTree = true) { this.fetchDiffFilesMeta() - .then(({ real_size = 0 }) => { - this.diffFilesLength = parseInt(real_size, 10) || 0; + .then((data) => { + let realSize = 0; + + if (data) { + realSize = data.real_size; + } + + this.diffFilesLength = parseInt(realSize, 10) || 0; if (toggleTree) { this.setTreeDisplay(); } diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 5234be44b05..c73012527a2 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -5,7 +5,7 @@ import { historyPushState, scrollToElement, } from '~/lib/utils/common_utils'; -import { createAlert } from '~/flash'; +import { createAlert, VARIANT_WARNING } from '~/flash'; import { diffViewerModes } from '~/ide/constants'; import axios from '~/lib/utils/axios_utils'; @@ -229,9 +229,17 @@ export const fetchDiffFilesMeta = ({ commit, state }) => { return data; }) - .catch(() => worker.terminate()); -}; + .catch((error) => { + worker.terminate(); + if (error.response.status === httpStatusCodes.NOT_FOUND) { + createAlert({ + message: __('Building your merge request. Wait a few moments, then refresh this page.'), + variant: VARIANT_WARNING, + }); + } + }); +}; export const fetchCoverageFiles = ({ commit, state }) => { const coveragePoll = new Poll({ resource: { diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue new file mode 100644 index 00000000000..2a1de2ae4a7 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue @@ -0,0 +1,61 @@ +<script> +import { GlModal } from '@gitlab/ui'; +import { __, n__ } from '~/locale'; +import { + DELETE_PACKAGES_MODAL_TITLE, + DELETE_PACKAGE_MODAL_PRIMARY_ACTION, +} from '~/packages_and_registries/package_registry/constants'; + +export default { + name: 'DeleteModal', + i18n: { + DELETE_PACKAGES_MODAL_TITLE, + }, + components: { + GlModal, + }, + props: { + itemsToBeDeleted: { + type: Array, + required: true, + }, + }, + computed: { + description() { + return n__( + 'PackageRegistry|You are about to delete 1 package. This operation is irreversible.', + `PackageRegistry|You are about to delete %d packages. This operation is irreversible.`, + this.itemsToBeDeleted.length, + ); + }, + }, + modal: { + packagesDeletePrimaryAction: { + text: DELETE_PACKAGE_MODAL_PRIMARY_ACTION, + attributes: [{ variant: 'danger' }, { category: 'primary' }], + }, + cancelAction: { + text: __('Cancel'), + }, + }, + methods: { + show() { + this.$refs.deleteModal.show(); + }, + }, +}; +</script> + +<template> + <gl-modal + ref="deleteModal" + size="sm" + modal-id="delete-packages-modal" + :action-primary="$options.modal.packagesDeletePrimaryAction" + :action-cancel="$options.modal.cancelAction" + :title="$options.i18n.DELETE_PACKAGES_MODAL_TITLE" + @primary="$emit('confirm')" + > + <span>{{ description }}</span> + </gl-modal> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue index 7a000aca0f2..4553dd3421b 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue @@ -2,6 +2,7 @@ import { GlDropdown, GlDropdownItem, + GlFormCheckbox, GlIcon, GlSprintf, GlTooltipDirective, @@ -26,6 +27,7 @@ export default { components: { GlDropdown, GlDropdownItem, + GlFormCheckbox, GlIcon, GlSprintf, GlTruncate, @@ -45,6 +47,11 @@ export default { type: Object, required: true, }, + selected: { + type: Boolean, + default: false, + required: false, + }, }, computed: { packageType() { @@ -90,7 +97,15 @@ export default { </script> <template> - <list-item data-testid="package-row"> + <list-item data-testid="package-row" v-bind="$attrs"> + <template #left-action> + <gl-form-checkbox + v-if="packageEntity.canDestroy" + class="gl-m-0" + :checked="selected" + @change="$emit('select')" + /> + </template> <template #left-primary> <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"> <router-link @@ -168,12 +183,9 @@ export default { category="tertiary" no-caret > - <gl-dropdown-item - data-testid="action-delete" - variant="danger" - @click="$emit('packageToDelete', packageEntity)" - >{{ $options.i18n.deletePackage }}</gl-dropdown-item - > + <gl-dropdown-item data-testid="action-delete" variant="danger" @click="$emit('delete')">{{ + $options.i18n.deletePackage + }}</gl-dropdown-item> </gl-dropdown> </template> </list-item> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue index 0043d778c60..ddcddf80c15 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue @@ -1,6 +1,6 @@ <script> import { GlAlert } from '@gitlab/ui'; -import { s__, sprintf } from '~/locale'; +import { s__, sprintf, n__ } from '~/locale'; import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue'; import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; @@ -48,6 +48,9 @@ export default { }; }, computed: { + listTitle() { + return n__('%d package', '%d packages', this.list.length); + }, isListEmpty() { return !this.list || this.list.length === 0; }, @@ -83,6 +86,14 @@ export default { this.itemToBeDeleted = { ...item }; this.track(REQUEST_DELETE_PACKAGE_TRACKING_ACTION); }, + setItemsToBeDeleted(items) { + if (items.length === 1) { + const [item] = items; + this.setItemToBeDeleted(item); + return; + } + this.$emit('delete', items); + }, deleteItemConfirmation() { this.$emit('package:delete', this.itemToBeDeleted); this.track(DELETE_PACKAGE_TRACKING_ACTION); @@ -124,15 +135,22 @@ export default { > <registry-list data-testid="packages-table" - :hidden-delete="true" :is-loading="isLoading" :items="list" :pagination="pageInfo" + :title="listTitle" + @delete="setItemsToBeDeleted" @prev-page="$emit('prev-page')" @next-page="$emit('next-page')" > - <template #default="{ item }"> - <packages-list-row :package-entity="item" @packageToDelete="setItemToBeDeleted(item)" /> + <template #default="{ selectItem, isSelected, item, first }"> + <packages-list-row + :first="first" + :package-entity="item" + :selected="isSelected(item)" + @delete="setItemToBeDeleted(item)" + @select="selectItem(item)" + /> </template> </registry-list> 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 006164fd462..b731cd77e66 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -110,6 +110,13 @@ export const FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE = s__( export const FETCH_PACKAGE_METADATA_ERROR_MESSAGE = s__( 'PackageRegistry|Something went wrong while fetching the package metadata.', ); +export const DELETE_PACKAGES_ERROR_MESSAGE = s__( + 'PackageRegistry|Something went wrong while deleting packages.', +); +export const DELETE_PACKAGES_SUCCESS_MESSAGE = s__('PackageRegistry|Packages deleted successfully'); + +export const DELETE_PACKAGES_MODAL_TITLE = s__('PackageRegistry|Delete packages'); +export const DELETE_PACKAGE_MODAL_PRIMARY_ACTION = s__('PackageRegistry|Permanently delete'); export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully'); export const PACKAGE_REGISTRY_TITLE = __('Package Registry'); diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql new file mode 100644 index 00000000000..e1ff5518bf8 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql @@ -0,0 +1,5 @@ +mutation destroyPackages($ids: [PackagesPackageID!]!) { + destroyPackages(input: { ids: $ids }) { + errors + } +} diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue index ea1432299ce..8b5d51cb856 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue @@ -1,5 +1,5 @@ <script> -import { GlBanner, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; +import { GlAlert, GlBanner, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; import { createAlert, VARIANT_INFO } from '~/flash'; import { getCookie, historyReplaceState, parseBoolean, setCookie } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; @@ -10,19 +10,23 @@ import { GRAPHQL_PAGE_SIZE, HIDE_PACKAGE_MIGRATION_SURVEY_COOKIE, DELETE_PACKAGE_SUCCESS_MESSAGE, + DELETE_PACKAGES_ERROR_MESSAGE, + DELETE_PACKAGES_SUCCESS_MESSAGE, EMPTY_LIST_HELP_URL, PACKAGE_HELP_URL, SURVEY_LINK, } from '~/packages_and_registries/package_registry/constants'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; - +import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue'; import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue'; import PackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue'; +import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; export default { components: { + GlAlert, GlBanner, GlEmptyState, GlLink, @@ -30,11 +34,14 @@ export default { PackageList, PackageTitle, PackageSearch, + DeleteModal, DeletePackage, }, inject: ['emptyListIllustration', 'isGroupPage', 'fullPath'], data() { return { + alertVariables: null, + itemsToBeDeleted: [], packages: {}, sort: '', filters: {}, @@ -114,6 +121,45 @@ export default { historyReplaceState(cleanUrl); } }, + async confirmDelete() { + const { itemsToBeDeleted } = this; + this.itemsToBeDeleted = []; + this.mutationLoading = true; + try { + const { data } = await this.$apollo.mutate({ + mutation: destroyPackagesMutation, + variables: { + ids: itemsToBeDeleted.map((i) => i.id), + }, + awaitRefetchQueries: true, + refetchQueries: [ + { + query: getPackagesQuery, + variables: { ...this.queryVariables, first: GRAPHQL_PAGE_SIZE }, + }, + ], + }); + + if (data?.destroyPackages?.errors[0]) { + throw new Error(data.destroyPackages.errors[0]); + } + this.showAlert({ + variant: 'success', + message: DELETE_PACKAGES_SUCCESS_MESSAGE, + }); + } catch { + this.showAlert({ + variant: 'danger', + message: DELETE_PACKAGES_ERROR_MESSAGE, + }); + } finally { + this.mutationLoading = false; + } + }, + showDeletePackagesModal(toBeDeleted) { + this.itemsToBeDeleted = toBeDeleted; + this.$refs.deletePackagesModal.show(); + }, handleSearchUpdate({ sort, filters }) { this.sort = sort; this.filters = { ...filters }; @@ -151,6 +197,9 @@ export default { updateQuery: this.updateQuery, }); }, + showAlert(obj) { + this.alertVariables = { ...obj }; + }, }, i18n: { widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'), @@ -175,6 +224,15 @@ export default { <template> <div> + <gl-alert + v-if="alertVariables" + :variant="alertVariables.variant" + class="gl-mt-5" + dismissible + @dismiss="alertVariables = null" + > + {{ alertVariables.message }} + </gl-alert> <gl-banner v-if="showSurveyBanner" :title="$options.i18n.surveyBannerTitle" @@ -187,7 +245,7 @@ export default { <p>{{ $options.i18n.surveyBannerDescription }}</p> </gl-banner> <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount" /> - <package-search @update="handleSearchUpdate" /> + <package-search class="gl-mb-5" @update="handleSearchUpdate" /> <delete-package :refetch-queries="refetchQueriesData" @@ -203,6 +261,7 @@ export default { @prev-page="fetchPreviousPage" @next-page="fetchNextPage" @package:delete="deletePackage" + @delete="showDeletePackagesModal" > <template #empty-state> <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration"> @@ -221,5 +280,11 @@ export default { </package-list> </template> </delete-package> + + <delete-modal + ref="deletePackagesModal" + :items-to-be-deleted="itemsToBeDeleted" + @confirm="confirmDelete" + /> </div> </template> diff --git a/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_package_forwarding_settings.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_package_forwarding_settings.mutation.graphql new file mode 100644 index 00000000000..e5e31f03a7d --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_package_forwarding_settings.mutation.graphql @@ -0,0 +1,16 @@ +mutation updatePackageForwardingSettings($input: UpdateNamespacePackageSettingsInput!) { + updateNamespacePackageSettings(input: $input) { + packageSettings { + mavenPackageRequestsForwarding + lockMavenPackageRequestsForwarding + mavenPackageRequestsForwardingLocked + npmPackageRequestsForwarding + lockNpmPackageRequestsForwarding + npmPackageRequestsForwardingLocked + pypiPackageRequestsForwarding + lockPypiPackageRequestsForwarding + pypiPackageRequestsForwardingLocked + } + errors + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue index 2802e4a90b9..0256eec6d56 100644 --- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue @@ -124,7 +124,7 @@ export default { </script> <template> - <div class="gl-pt-2"> + <div> <gl-loading-icon v-if="$apollo.queries.pipeline.loading" /> <pipeline-mini-graph v-else diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue index 37138955415..f2782f96da1 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue @@ -45,7 +45,7 @@ export default { </script> <template> - <nav> + <nav class="search-filter"> <gl-nav vertical pills> <gl-nav-item v-for="(item, scope, index) in navigation" diff --git a/config/initializers/memory_watchdog.rb b/config/initializers/memory_watchdog.rb index 8a540414378..99c5d61293f 100644 --- a/config/initializers/memory_watchdog.rb +++ b/config/initializers/memory_watchdog.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true return unless Gitlab::Runtime.application? -return unless Gitlab::Utils.to_boolean(ENV['GITLAB_MEMORY_WATCHDOG_ENABLED']) +return unless Gitlab::Utils.to_boolean(ENV['GITLAB_MEMORY_WATCHDOG_ENABLED'], default: Gitlab::Runtime.puma?) Gitlab::Cluster::LifecycleEvents.on_worker_start do watchdog = Gitlab::Memory::Watchdog.new diff --git a/config/initializers_before_autoloader/000_inflections.rb b/config/initializers_before_autoloader/000_inflections.rb index cb4ea8d64e4..795b0f20128 100644 --- a/config/initializers_before_autoloader/000_inflections.rb +++ b/config/initializers_before_autoloader/000_inflections.rb @@ -17,6 +17,7 @@ ActiveSupport::Inflector.inflections do |inflect| award_emoji ci_secure_file_registry container_repository_registry + dependency_proxy_blob_registry design_registry event_log file_registry diff --git a/db/docs/dependency_proxy_blob_states.yml b/db/docs/dependency_proxy_blob_states.yml new file mode 100644 index 00000000000..ddb9414b5f8 --- /dev/null +++ b/db/docs/dependency_proxy_blob_states.yml @@ -0,0 +1,9 @@ +--- +table_name: dependency_proxy_blob_states +classes: + - Geo::DependencyProxyBlobState +feature_categories: + - geo_replication +description: Separate table for dependency proxy blob verification states +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101429 +milestone: '15.6' diff --git a/db/migrate/20221018202524_create_dependency_proxy_blob_states.rb b/db/migrate/20221018202524_create_dependency_proxy_blob_states.rb new file mode 100644 index 00000000000..b042df43f04 --- /dev/null +++ b/db/migrate/20221018202524_create_dependency_proxy_blob_states.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class CreateDependencyProxyBlobStates < Gitlab::Database::Migration[2.0] + VERIFICATION_STATE_INDEX_NAME = "index_dependency_proxy_blob_states_on_verification_state" + PENDING_VERIFICATION_INDEX_NAME = "index_dependency_proxy_blob_states_pending_verification" + FAILED_VERIFICATION_INDEX_NAME = "index_dependency_proxy_blob_states_failed_verification" + NEEDS_VERIFICATION_INDEX_NAME = "index_dependency_proxy_blob_states_needs_verification" + + enable_lock_retries! + + def up + table_comment = { + owner: 'group::geo', + description: 'Geo-specific table to store the verification state of DependencyProxy::Blob objects' + } + + create_table :dependency_proxy_blob_states, id: false, comment: Gitlab::Json.dump(table_comment) do |t| + t.datetime_with_timezone :verification_started_at + t.datetime_with_timezone :verification_retry_at + t.datetime_with_timezone :verified_at + t.references :dependency_proxy_blob, + primary_key: true, + default: nil, + index: true, + foreign_key: { on_delete: :cascade } + t.integer :verification_state, default: 0, limit: 2, null: false + t.integer :verification_retry_count, default: 0, limit: 2, null: false + t.binary :verification_checksum, using: 'verification_checksum::bytea' + t.text :verification_failure, limit: 255 + + t.index :verification_state, name: VERIFICATION_STATE_INDEX_NAME + t.index :verified_at, + where: "(verification_state = 0)", + order: { verified_at: 'ASC NULLS FIRST' }, + name: PENDING_VERIFICATION_INDEX_NAME + t.index :verification_retry_at, + where: "(verification_state = 3)", + order: { verification_retry_at: 'ASC NULLS FIRST' }, + name: FAILED_VERIFICATION_INDEX_NAME + t.index :verification_state, + where: "(verification_state = 0 OR verification_state = 3)", + name: NEEDS_VERIFICATION_INDEX_NAME + end + end + + def down + drop_table :dependency_proxy_blob_states + end +end diff --git a/db/schema_migrations/20221018202524 b/db/schema_migrations/20221018202524 new file mode 100644 index 00000000000..ee738f3608c --- /dev/null +++ b/db/schema_migrations/20221018202524 @@ -0,0 +1 @@ +a3266078f4760f0f5a4c7a43669cea1170924f29d6867e712620c2234dbf13c6
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 7c3182ba0b2..f863dbd2cec 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14586,6 +14586,20 @@ CREATE SEQUENCE dast_sites_id_seq ALTER SEQUENCE dast_sites_id_seq OWNED BY dast_sites.id; +CREATE TABLE dependency_proxy_blob_states ( + verification_started_at timestamp with time zone, + verification_retry_at timestamp with time zone, + verified_at timestamp with time zone, + dependency_proxy_blob_id bigint NOT NULL, + verification_state smallint DEFAULT 0 NOT NULL, + verification_retry_count smallint DEFAULT 0 NOT NULL, + verification_checksum bytea, + verification_failure text, + CONSTRAINT check_8e4f76fffe CHECK ((char_length(verification_failure) <= 255)) +); + +COMMENT ON TABLE dependency_proxy_blob_states IS '{"owner":"group::geo","description":"Geo-specific table to store the verification state of DependencyProxy::Blob objects"}'; + CREATE TABLE dependency_proxy_blobs ( id integer NOT NULL, group_id integer NOT NULL, @@ -25558,6 +25572,9 @@ ALTER TABLE ONLY dast_site_validations ALTER TABLE ONLY dast_sites ADD CONSTRAINT dast_sites_pkey PRIMARY KEY (id); +ALTER TABLE ONLY dependency_proxy_blob_states + ADD CONSTRAINT dependency_proxy_blob_states_pkey PRIMARY KEY (dependency_proxy_blob_id); + ALTER TABLE ONLY dependency_proxy_blobs ADD CONSTRAINT dependency_proxy_blobs_pkey PRIMARY KEY (id); @@ -28785,6 +28802,16 @@ CREATE UNIQUE INDEX index_dast_sites_on_project_id_and_url ON dast_sites USING b CREATE UNIQUE INDEX index_dep_prox_manifests_on_group_id_file_name_and_status ON dependency_proxy_manifests USING btree (group_id, file_name, status); +CREATE INDEX index_dependency_proxy_blob_states_failed_verification ON dependency_proxy_blob_states USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3); + +CREATE INDEX index_dependency_proxy_blob_states_needs_verification ON dependency_proxy_blob_states USING btree (verification_state) WHERE ((verification_state = 0) OR (verification_state = 3)); + +CREATE INDEX index_dependency_proxy_blob_states_on_dependency_proxy_blob_id ON dependency_proxy_blob_states USING btree (dependency_proxy_blob_id); + +CREATE INDEX index_dependency_proxy_blob_states_on_verification_state ON dependency_proxy_blob_states USING btree (verification_state); + +CREATE INDEX index_dependency_proxy_blob_states_pending_verification ON dependency_proxy_blob_states USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0); + CREATE INDEX index_dependency_proxy_blobs_on_group_id_and_file_name ON dependency_proxy_blobs USING btree (group_id, file_name); CREATE INDEX index_dependency_proxy_blobs_on_group_id_status_read_at_id ON dependency_proxy_blobs USING btree (group_id, status, read_at, id); @@ -34099,6 +34126,9 @@ ALTER TABLE ONLY project_metrics_settings ALTER TABLE ONLY prometheus_metrics ADD CONSTRAINT fk_rails_4c8957a707 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY dependency_proxy_blob_states + ADD CONSTRAINT fk_rails_4cdbb92cbd FOREIGN KEY (dependency_proxy_blob_id) REFERENCES dependency_proxy_blobs(id) ON DELETE CASCADE; + ALTER TABLE ONLY scim_identities ADD CONSTRAINT fk_rails_4d2056ebd9 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 9adfabb0ab2..d3b28fd15bc 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -321,6 +321,16 @@ configuration option in `gitlab.yml`. These metrics are served from the | `geo_ci_secure_files_verification_total` | Gauge | 15.3 | Number of secure files verifications tried on secondary | `url` | | `geo_ci_secure_files_verified` | Gauge | 15.3 | Number of secure files verified on secondary | `url` | | `geo_ci_secure_files_verification_failed` | Gauge | 15.3 | Number of secure files verifications failed on secondary | `url` | +| `geo_dependency_proxy_blob` | Gauge | 15.6 | Number of dependency proxy blobs on primary | | +| `geo_dependency_proxy_blob_checksum_total` | Gauge | 15.6 | Number of dependency proxy blobs tried to checksum on primary | | +| `geo_dependency_proxy_blob_checksummed` | Gauge | 15.6 | Number of dependency proxy blobs successfully checksummed on primary | | +| `geo_dependency_proxy_blob_checksum_failed` | Gauge | 15.6 | Number of dependency proxy blobs failed to calculate the checksum on primary | | +| `geo_dependency_proxy_blob_synced` | Gauge | 15.6 | Number of dependency proxy blobs synced on secondary | | +| `geo_dependency_proxy_blob_failed` | Gauge | 15.6 | Number of dependency proxy blobs failed to sync on secondary | | +| `geo_dependency_proxy_blob_registry` | Gauge | 15.6 | Number of dependency proxy blobs in the registry | | +| `geo_dependency_proxy_blob_verification_total` | Gauge | 15.6 | Number of dependency proxy blobs verifications tried on secondary | | +| `geo_dependency_proxy_blob_verified` | Gauge | 15.6 | Number of dependency proxy blobs verified on secondary | | +| `geo_dependency_proxy_blob_verification_failed` | Gauge | 15.6 | Number of dependency proxy blobs verifications failed on secondary | | ## Database load balancing metrics **(PREMIUM SELF)** diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md index fe25b6661a0..00380e1624b 100644 --- a/doc/api/geo_nodes.md +++ b/doc/api/geo_nodes.md @@ -504,6 +504,19 @@ Example response: "ci_secure_files_synced_in_percentage": "100.00%", "ci_secure_files_verified_in_percentage": "100.00%", "ci_secure_files_synced_missing_on_primary_count": 0, + "dependency_proxy_blobs_count": 5, + "dependency_proxy_blobs_checksum_total_count": 5, + "dependency_proxy_blobs_checksummed_count": 5, + "dependency_proxy_blobs_checksum_failed_count": 0, + "dependency_proxy_blobs_synced_count": 5, + "dependency_proxy_blobs_failed_count": 0, + "dependency_proxy_blobs_registry_count": 5, + "dependency_proxy_blobs_verification_total_count": 5, + "dependency_proxy_blobs_verified_count": 5, + "dependency_proxy_blobs_verification_failed_count": 0, + "dependency_proxy_blobs_synced_in_percentage": "100.00%", + "dependency_proxy_blobs_verified_in_percentage": "100.00%", + "dependency_proxy_blobs_synced_missing_on_primary_count": 0, "container_repositories_count": 5, "container_repositories_synced_count": 5, "container_repositories_failed_count": 0, @@ -675,6 +688,19 @@ Example response: "job_artifacts_synced_in_percentage": "100.00%", "job_artifacts_verified_in_percentage": "100.00%", "job_artifacts_synced_missing_on_primary_count": 0, + "dependency_proxy_blobs_count": 5, + "dependency_proxy_blobs_checksum_total_count": 5, + "dependency_proxy_blobs_checksummed_count": 5, + "dependency_proxy_blobs_checksum_failed_count": 0, + "dependency_proxy_blobs_synced_count": 5, + "dependency_proxy_blobs_failed_count": 0, + "dependency_proxy_blobs_registry_count": 5, + "dependency_proxy_blobs_verification_total_count": 5, + "dependency_proxy_blobs_verified_count": 5, + "dependency_proxy_blobs_verification_failed_count": 0, + "dependency_proxy_blobs_synced_in_percentage": "100.00%", + "dependency_proxy_blobs_verified_in_percentage": "100.00%", + "dependency_proxy_blobs_synced_missing_on_primary_count": 0, "container_repositories_count": 5, "container_repositories_synced_count": 5, "container_repositories_failed_count": 0, @@ -856,6 +882,19 @@ Example response: "ci_secure_files_synced_in_percentage": "100.00%", "ci_secure_files_verified_in_percentage": "100.00%", "ci_secure_files_synced_missing_on_primary_count": 0, + "dependency_proxy_blobs_count": 5, + "dependency_proxy_blobs_checksum_total_count": 5, + "dependency_proxy_blobs_checksummed_count": 5, + "dependency_proxy_blobs_checksum_failed_count": 0, + "dependency_proxy_blobs_synced_count": 5, + "dependency_proxy_blobs_failed_count": 0, + "dependency_proxy_blobs_registry_count": 5, + "dependency_proxy_blobs_verification_total_count": 5, + "dependency_proxy_blobs_verified_count": 5, + "dependency_proxy_blobs_verification_failed_count": 0, + "dependency_proxy_blobs_synced_in_percentage": "100.00%", + "dependency_proxy_blobs_verified_in_percentage": "100.00%", + "dependency_proxy_blobs_synced_missing_on_primary_count": 0, "container_repositories_count": 5, "container_repositories_synced_count": 5, "container_repositories_failed_count": 0, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index a71790d4c9a..2eb935b229e 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -7283,6 +7283,29 @@ The edge type for [`DependencyProxyBlob`](#dependencyproxyblob). | <a id="dependencyproxyblobedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="dependencyproxyblobedgenode"></a>`node` | [`DependencyProxyBlob`](#dependencyproxyblob) | The item at the end of the edge. | +#### `DependencyProxyBlobRegistryConnection` + +The connection type for [`DependencyProxyBlobRegistry`](#dependencyproxyblobregistry). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxyblobregistryconnectionedges"></a>`edges` | [`[DependencyProxyBlobRegistryEdge]`](#dependencyproxyblobregistryedge) | A list of edges. | +| <a id="dependencyproxyblobregistryconnectionnodes"></a>`nodes` | [`[DependencyProxyBlobRegistry]`](#dependencyproxyblobregistry) | A list of nodes. | +| <a id="dependencyproxyblobregistryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `DependencyProxyBlobRegistryEdge` + +The edge type for [`DependencyProxyBlobRegistry`](#dependencyproxyblobregistry). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxyblobregistryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="dependencyproxyblobregistryedgenode"></a>`node` | [`DependencyProxyBlobRegistry`](#dependencyproxyblobregistry) | The item at the end of the edge. | + #### `DependencyProxyManifestConnection` The connection type for [`DependencyProxyManifest`](#dependencyproxymanifest). @@ -11693,6 +11716,25 @@ Dependency proxy blob. | <a id="dependencyproxyblobsize"></a>`size` | [`String!`](#string) | Size of the blob file. | | <a id="dependencyproxyblobupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. | +### `DependencyProxyBlobRegistry` + +Represents the Geo replication and verification state of a dependency_proxy_blob. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxyblobregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the DependencyProxyBlobRegistry was created. | +| <a id="dependencyproxyblobregistrydependencyproxyblobid"></a>`dependencyProxyBlobId` | [`ID!`](#id) | ID of the Dependency Proxy Blob. | +| <a id="dependencyproxyblobregistryid"></a>`id` | [`ID!`](#id) | ID of the DependencyProxyBlobRegistry. | +| <a id="dependencyproxyblobregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the DependencyProxyBlobRegistry. | +| <a id="dependencyproxyblobregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the DependencyProxyBlobRegistry. | +| <a id="dependencyproxyblobregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the DependencyProxyBlobRegistry is resynced. | +| <a id="dependencyproxyblobregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the DependencyProxyBlobRegistry. | +| <a id="dependencyproxyblobregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the DependencyProxyBlobRegistry. | +| <a id="dependencyproxyblobregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the DependencyProxyBlobRegistry is reverified. | +| <a id="dependencyproxyblobregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the DependencyProxyBlobRegistry. | + ### `DependencyProxyImageTtlGroupPolicy` Group-level Dependency Proxy TTL policy settings. @@ -12898,6 +12940,28 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="geonodecontainerrepositoryregistriesreplicationstate"></a>`replicationState` | [`ReplicationStateEnum`](#replicationstateenum) | Filters registries by their replication state. | | <a id="geonodecontainerrepositoryregistriesverificationstate"></a>`verificationState` | [`VerificationStateEnum`](#verificationstateenum) | Filters registries by their verification state. | +##### `GeoNode.dependencyProxyBlobRegistries` + +Find Dependency Proxy Blob registries on this Geo node. Ignored if `geo_dependency_proxy_blob_replication` feature flag is disabled. + +WARNING: +**Introduced** in 15.6. +This feature is in Alpha. It can be changed or removed at any time. + +Returns [`DependencyProxyBlobRegistryConnection`](#dependencyproxyblobregistryconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="geonodedependencyproxyblobregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. | +| <a id="geonodedependencyproxyblobregistriesreplicationstate"></a>`replicationState` | [`ReplicationStateEnum`](#replicationstateenum) | Filters registries by their replication state. | +| <a id="geonodedependencyproxyblobregistriesverificationstate"></a>`verificationState` | [`VerificationStateEnum`](#verificationstateenum) | Filters registries by their verification state. | + ##### `GeoNode.groupWikiRepositoryRegistries` Find group wiki repository registries on this Geo node. diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md index b3c18b82211..0b6fa25c5c7 100644 --- a/doc/api/suggestions.md +++ b/doc/api/suggestions.md @@ -38,7 +38,7 @@ Example response: "to_line": 10, "applicable": true, "applied": false, - "from_content": "This is an example\n", + "from_content": "This is an eaxmple\n", "to_content": "This is an example\n" } ``` @@ -68,7 +68,7 @@ Example response: "to_line": 10, "applicable": true, "applied": false, - "from_content": "This is an example\n", + "from_content": "This is an eaxmple\n", "to_content": "This is an example\n" } { diff --git a/doc/architecture/blueprints/_template.md b/doc/architecture/blueprints/_template.md index 01c2ab3595e..798d51a97ad 100644 --- a/doc/architecture/blueprints/_template.md +++ b/doc/architecture/blueprints/_template.md @@ -53,11 +53,6 @@ as part of any review. --> <!-- -Start the document by pasting an exact copy of the disclaimer found at: -https://docs.gitlab.com/ee/development/documentation/versions.html#legal-disclaimer-for-future-features ---> - -<!-- For long pages, consider creating a table of contents. The `[_TOC_]` function is not supported on docs.gitlab.com. --> diff --git a/doc/architecture/blueprints/ci_data_decay/index.md b/doc/architecture/blueprints/ci_data_decay/index.md index 951598afd69..b7c3bdde2f8 100644 --- a/doc/architecture/blueprints/ci_data_decay/index.md +++ b/doc/architecture/blueprints/ci_data_decay/index.md @@ -10,14 +10,6 @@ participating-stages: [] # CI/CD data time decay -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Summary GitLab CI/CD is one of the most data and compute intensive components of GitLab. diff --git a/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md b/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md index 3ee7640940e..d61412ae1ed 100644 --- a/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md +++ b/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md @@ -7,14 +7,6 @@ description: 'Pipeline data partitioning design' # Pipeline data partitioning design -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## What problem are we trying to solve? We want to partition the CI/CD dataset, because some of the database tables are diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md index 1a909913e49..a3c72227f3e 100644 --- a/doc/architecture/blueprints/ci_pipeline_components/index.md +++ b/doc/architecture/blueprints/ci_pipeline_components/index.md @@ -10,14 +10,6 @@ participating-stages: [] # CI/CD pipeline components catalog -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Summary ## Goals diff --git a/doc/architecture/blueprints/ci_scale/index.md b/doc/architecture/blueprints/ci_scale/index.md index 6922934eb4b..c02fb35974b 100644 --- a/doc/architecture/blueprints/ci_scale/index.md +++ b/doc/architecture/blueprints/ci_scale/index.md @@ -7,14 +7,6 @@ description: 'Improve scalability of GitLab CI/CD' # CI/CD Scaling -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Summary GitLab CI/CD is one of the most data and compute intensive components of GitLab. diff --git a/doc/architecture/blueprints/cloud_native_build_logs/index.md b/doc/architecture/blueprints/cloud_native_build_logs/index.md index 17eace53d2f..20cfb46abc4 100644 --- a/doc/architecture/blueprints/cloud_native_build_logs/index.md +++ b/doc/architecture/blueprints/cloud_native_build_logs/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Cloud Native Build Logs -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - Cloud native and the adoption of Kubernetes has been recognised by GitLab to be one of the top two biggest tailwinds that are helping us grow faster as a company behind the project. diff --git a/doc/architecture/blueprints/cloud_native_gitlab_pages/index.md b/doc/architecture/blueprints/cloud_native_gitlab_pages/index.md index bf4d0974bdd..b6f3a59dc0b 100644 --- a/doc/architecture/blueprints/cloud_native_gitlab_pages/index.md +++ b/doc/architecture/blueprints/cloud_native_gitlab_pages/index.md @@ -10,14 +10,6 @@ participating-stages: [] # GitLab Pages New Architecture -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - GitLab Pages is an important component of the GitLab product. It is mostly being used to serve static content, and has a limited set of well defined responsibilities. That being said, unfortunately it has become a blocker for diff --git a/doc/architecture/blueprints/composable_codebase_using_rails_engines/index.md b/doc/architecture/blueprints/composable_codebase_using_rails_engines/index.md index cbce34a7f17..53f38fa85fd 100644 --- a/doc/architecture/blueprints/composable_codebase_using_rails_engines/index.md +++ b/doc/architecture/blueprints/composable_codebase_using_rails_engines/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Composable GitLab codebase - using Rails Engines -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - NOTE: Due to our focus on improving the overall availability of GitLab.com and reducing tech debt, we do not have capacity to act on this blueprint. We will re-evaluate in Q1-FY23. diff --git a/doc/architecture/blueprints/consolidating_groups_and_projects/index.md b/doc/architecture/blueprints/consolidating_groups_and_projects/index.md index a1236bd5452..0818d9b973d 100644 --- a/doc/architecture/blueprints/consolidating_groups_and_projects/index.md +++ b/doc/architecture/blueprints/consolidating_groups_and_projects/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Consolidating Groups and Projects -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - There are numerous features that exist exclusively within groups or projects. The boundary between group and project features used to be clear. However, there is growing demand to have group features within projects, and diff --git a/doc/architecture/blueprints/container_registry_metadata_database/index.md b/doc/architecture/blueprints/container_registry_metadata_database/index.md index ccdde84fc68..63e27286756 100644 --- a/doc/architecture/blueprints/container_registry_metadata_database/index.md +++ b/doc/architecture/blueprints/container_registry_metadata_database/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Container Registry Metadata Database -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Usage of the GitLab Container Registry With the [Container Registry](https://gitlab.com/gitlab-org/container-registry) integrated into GitLab, every GitLab project can have its own space to store its Docker images. You can use the registry to build, push and share images using the Docker client, CI/CD or the GitLab API. diff --git a/doc/architecture/blueprints/database/scalability/patterns/read_mostly.md b/doc/architecture/blueprints/database/scalability/patterns/read_mostly.md index 09b7d3f5f1c..ec236c9bfe3 100644 --- a/doc/architecture/blueprints/database/scalability/patterns/read_mostly.md +++ b/doc/architecture/blueprints/database/scalability/patterns/read_mostly.md @@ -10,14 +10,6 @@ description: 'Learn how to scale operating on read-mostly data at scale' > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326037) in GitLab 14.0. -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document describes the *read-mostly* pattern introduced in the [Database Scalability Working Group](https://about.gitlab.com/company/team/structure/working-groups/database-scalability/#read-mostly-data). We discuss the characteristics of *read-mostly* data and propose best practices for GitLab development diff --git a/doc/architecture/blueprints/database/scalability/patterns/time_decay.md b/doc/architecture/blueprints/database/scalability/patterns/time_decay.md index fd5612e27bd..93f5dffd3f5 100644 --- a/doc/architecture/blueprints/database/scalability/patterns/time_decay.md +++ b/doc/architecture/blueprints/database/scalability/patterns/time_decay.md @@ -10,14 +10,6 @@ description: 'Learn how to operate on large time-decay data' > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326035) in GitLab 14.0. -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document describes the *time-decay pattern* introduced in the [Database Scalability Working Group](https://about.gitlab.com/company/team/structure/working-groups/database-scalability/#time-decay-data). We discuss the characteristics of time-decay data, and propose best practices for GitLab development diff --git a/doc/architecture/blueprints/database_scaling/size-limits.md b/doc/architecture/blueprints/database_scaling/size-limits.md index 42654a9979f..e530bd6eff0 100644 --- a/doc/architecture/blueprints/database_scaling/size-limits.md +++ b/doc/architecture/blueprints/database_scaling/size-limits.md @@ -7,14 +7,6 @@ description: 'Database Scalability / Limit table sizes' # Database Scalability: Limit on-disk table size to < 100 GB for GitLab.com -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a proposal to work towards reducing and limiting table sizes on GitLab.com. We establish a **measurable target** by limiting table size to a certain threshold. This is used as an indicator to drive database focus and decision making. With GitLab.com growing, we continuously re-evaluate which tables need to be worked on to prevent or otherwise fix violations. This is not meant to be a hard rule but rather a strong indication that work needs to be done to break a table apart or otherwise reduce its size. diff --git a/doc/architecture/blueprints/database_testing/index.md b/doc/architecture/blueprints/database_testing/index.md index e50190666b6..fe6dcf1723d 100644 --- a/doc/architecture/blueprints/database_testing/index.md +++ b/doc/architecture/blueprints/database_testing/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Database Testing -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - We have identified [common themes of reverted migrations](https://gitlab.com/gitlab-org/gitlab/-/issues/233391) and discovered failed migrations breaking in both production and staging even when successfully tested in a developer environment. We have also experienced production incidents even with successful testing in staging. These failures are quite expensive: they can have a significant effect on availability, block deployments, and generate incident escalations. These escalations must be triaged and either reverted or fixed forward. Often, this can take place without the original author's involvement due to time zones and/or the criticality of the escalation. With our increased deployment speeds and stricter uptime requirements, the need for improving database testing is critical, particularly earlier in the development process (shift left). From a developer's perspective, it is hard, if not unfeasible, to validate a migration on a large enough dataset before it goes into production. diff --git a/doc/architecture/blueprints/feature_flags_development/index.md b/doc/architecture/blueprints/feature_flags_development/index.md index 10ab83de3d1..730daf56f0d 100644 --- a/doc/architecture/blueprints/feature_flags_development/index.md +++ b/doc/architecture/blueprints/feature_flags_development/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Architectural discussion of feature flags -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - Usage of feature flags become crucial for the development of GitLab. The feature flags are a convenient way to ship changes early, and safely rollout them to wide audience ensuring that feature is stable and performant. diff --git a/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md b/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md index c0030e63dc9..6ac67dd0f18 100644 --- a/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md +++ b/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md @@ -10,14 +10,6 @@ participating-stages: [] # GitLab to Kubernetes communication **(FREE)** -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - The goal of this document is to define how GitLab can communicate with Kubernetes and in-cluster services through the GitLab agent. diff --git a/doc/architecture/blueprints/graphql_api/index.md b/doc/architecture/blueprints/graphql_api/index.md index cc08f12f549..4b446a78541 100644 --- a/doc/architecture/blueprints/graphql_api/index.md +++ b/doc/architecture/blueprints/graphql_api/index.md @@ -10,14 +10,6 @@ participating-stages: [] # GraphQL API -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - [GraphQL](https://graphql.org/) is a data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. diff --git a/doc/architecture/blueprints/image_resizing/index.md b/doc/architecture/blueprints/image_resizing/index.md index e96fd67008e..948378d8834 100644 --- a/doc/architecture/blueprints/image_resizing/index.md +++ b/doc/architecture/blueprints/image_resizing/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Image resizing for avatars and content images -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - Currently, we are showing all uploaded images 1:1, which is of course not ideal. To improve performance greatly, add image resizing to the backend. There are two main areas of image resizing to consider; avatars and content images. The MVC for this implementation focuses on Avatars. Avatars requests consist of approximately 70% of total image requests. There is an identified set of sizes we intend to support which makes the scope of this first MVC very narrow. Content image resizing has many more considerations for size and features. It is entirely possible that we have two separate development efforts with the same goal of increasing performance via image resizing. ## MVC Avatar Resizing diff --git a/doc/architecture/blueprints/object_storage/index.md b/doc/architecture/blueprints/object_storage/index.md index d1593fb4eb0..61dc37d7706 100644 --- a/doc/architecture/blueprints/object_storage/index.md +++ b/doc/architecture/blueprints/object_storage/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Object storage: `direct_upload` consolidation -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Abstract GitLab stores three classes of user data: database records, Git diff --git a/doc/architecture/blueprints/pods/index.md b/doc/architecture/blueprints/pods/index.md index dfd046a40e8..527720d003a 100644 --- a/doc/architecture/blueprints/pods/index.md +++ b/doc/architecture/blueprints/pods/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Pods -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a work-in-progress and represents a very early state of the Pods design. Significant aspects are not documented, though we expect to add them in the future. ## Summary diff --git a/doc/architecture/blueprints/pods/pods-feature-git-access.md b/doc/architecture/blueprints/pods/pods-feature-git-access.md index 4f4d7b91420..ae996281d46 100644 --- a/doc/architecture/blueprints/pods/pods-feature-git-access.md +++ b/doc/architecture/blueprints/pods/pods-feature-git-access.md @@ -5,14 +5,6 @@ comments: false description: 'Pods: Git Access' --- -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a work-in-progress and represents a very early state of the Pods design. Significant aspects are not documented, though we expect to add them in the future. This is one possible architecture for Pods, and we intend to diff --git a/doc/architecture/blueprints/pods/pods-feature-template.md b/doc/architecture/blueprints/pods/pods-feature-template.md index 8971822ebee..fd9813710f2 100644 --- a/doc/architecture/blueprints/pods/pods-feature-template.md +++ b/doc/architecture/blueprints/pods/pods-feature-template.md @@ -5,14 +5,6 @@ comments: false description: 'Pods architecture: Problem A' --- -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a work-in-progress and represents a very early state of the Pods design. Significant aspects are not documented, though we expect to add them in the future. This is one possible architecture for Pods, and we intend to diff --git a/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md b/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md index 03bb7a255c0..21aa72273fe 100644 --- a/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md +++ b/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md @@ -5,14 +5,6 @@ comments: false description: 'Pods Stateless Router Proposal' --- -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a work-in-progress and represents a very early state of the Pods design. Significant aspects are not documented, though we expect to add them in the future. This is one possible architecture for Pods, and we intend to diff --git a/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md b/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md index 6d3f27d9ae2..e7520f3d6a8 100644 --- a/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md +++ b/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md @@ -5,14 +5,6 @@ comments: false description: 'Pods Stateless Router Proposal' --- -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a work-in-progress and represents a very early state of the Pods design. Significant aspects are not documented, though we expect to add them in the future. This is one possible architecture for Pods, and we intend to diff --git a/doc/architecture/blueprints/rate_limiting/index.md b/doc/architecture/blueprints/rate_limiting/index.md index eac0fbb05f8..ffe0712d69b 100644 --- a/doc/architecture/blueprints/rate_limiting/index.md +++ b/doc/architecture/blueprints/rate_limiting/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Next Rate Limiting Architecture -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Summary Introducing reasonable application limits is a very important step in any SaaS diff --git a/doc/architecture/blueprints/runner_scaling/index.md b/doc/architecture/blueprints/runner_scaling/index.md index f7c477b4154..24c6820f94a 100644 --- a/doc/architecture/blueprints/runner_scaling/index.md +++ b/doc/architecture/blueprints/runner_scaling/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Next Runner Auto-scaling Architecture -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Summary GitLab Runner is a core component of GitLab CI/CD. It makes it possible to run diff --git a/doc/architecture/blueprints/runner_tokens/index.md b/doc/architecture/blueprints/runner_tokens/index.md index f9f53d0a03e..3f8a27e503d 100644 --- a/doc/architecture/blueprints/runner_tokens/index.md +++ b/doc/architecture/blueprints/runner_tokens/index.md @@ -7,14 +7,6 @@ description: 'Next Runner Token Architecture' # Next GitLab Runner Token Architecture -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - ## Summary GitLab Runner is a core component of GitLab CI/CD that runs diff --git a/doc/architecture/blueprints/work_items/index.md b/doc/architecture/blueprints/work_items/index.md index edc092f12b0..75a9d8d76ad 100644 --- a/doc/architecture/blueprints/work_items/index.md +++ b/doc/architecture/blueprints/work_items/index.md @@ -10,14 +10,6 @@ participating-stages: [] # Work Items -DISCLAIMER: -This page contains information related to upcoming products, features, and functionality. -It is important to note that the information presented is for informational purposes only. -Please do not rely on this information for purchasing or planning purposes. -As with all projects, the items mentioned on this page are subject to change or delay. -The development, release, and timing of any products, features, or functionality remain at the -sole discretion of GitLab Inc. - This document is a work-in-progress. Some aspects are not documented, though we expect to add them in the future. ## Summary diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md index caffd121ca4..e29d38bb22f 100644 --- a/doc/development/pipelines/index.md +++ b/doc/development/pipelines/index.md @@ -85,7 +85,7 @@ In this mode, `jest` would resolve all the dependencies of related to the change In addition, there are a few circumstances where we would always run the full Jest tests: -- when the `pipeline:run-all-jest` label is set on the merge request or is set by triage automation when the merge request is approved by any reviewer +- when the `pipeline:run-all-jest` label is set on the merge request - when the merge request is created by an automation (for example, Gitaly update or MR targeting a stable branch) - when the merge request is created in a security mirror - when any CI configuration file is changed (for example, `.gitlab-ci.yml` or `.gitlab/ci/**/*`) diff --git a/doc/development/pipelines/internals.md b/doc/development/pipelines/internals.md index a3774e20e96..2861e2f266b 100644 --- a/doc/development/pipelines/internals.md +++ b/doc/development/pipelines/internals.md @@ -204,7 +204,7 @@ and included in `rules` definitions via [YAML anchors](../../ci/yaml/yaml_optimi | `yaml-lint-patterns` | Only create job for YAML-related changes. | | `docs-patterns` | Only create job for docs-related changes. | | `frontend-dependency-patterns` | Only create job when frontend dependencies are updated (that is, `package.json`, and `yarn.lock`). changes. | -| `frontend-patterns` | Only create job for frontend-related changes. | +| `frontend-patterns-for-as-if-foss` | Only create job for frontend-related changes that have impact on FOSS. | | `backend-patterns` | Only create job for backend-related changes. | | `db-patterns` | Only create job for DB-related changes. | | `backstage-patterns` | Only create job for backstage-related changes (that is, Danger, fixtures, RuboCop, specs). | diff --git a/doc/user/infrastructure/iac/index.md b/doc/user/infrastructure/iac/index.md index c2285ecc773..866b16652fa 100644 --- a/doc/user/infrastructure/iac/index.md +++ b/doc/user/infrastructure/iac/index.md @@ -99,8 +99,8 @@ in the template you fetched to customize your configuration. - To collaborate on Terraform code changes and Infrastructure-as-Code workflows, use the [Terraform integration in merge requests](mr_integration.md). - To manage GitLab resources like users, groups, and projects, use the - [GitLab Terraform provider](https://github.com/gitlabhq/terraform-provider-gitlab). It is released separately from GitLab - and its documentation is available on [the Terraform docs site](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs). + [GitLab Terraform provider](https://gitlab.com/gitlab-org/terraform-provider-gitlab). + The GitLab Terraform provider documentation is available on [the Terraform docs site](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs). - [Create a new cluster on Amazon Elastic Kubernetes Service (EKS)](../clusters/connect/new_eks_cluster.md). - [Create a new cluster on Google Kubernetes Engine (GKE)](../clusters/connect/new_gke_cluster.md). - [Troubleshoot](troubleshooting.md) issues with GitLab and Terraform. diff --git a/lib/api/entities/metadata.rb b/lib/api/entities/metadata.rb index daa491ec42a..1e04b5c5982 100644 --- a/lib/api/entities/metadata.rb +++ b/lib/api/entities/metadata.rb @@ -3,12 +3,12 @@ module API module Entities class Metadata < Grape::Entity - expose :version - expose :revision + expose :version, documentation: { type: 'string', example: '15.2-pre' } + expose :revision, documentation: { type: 'string', example: 'c401a659d0c' } expose :kas do expose :enabled, documentation: { type: 'boolean' } - expose :externalUrl - expose :version + expose :externalUrl, documentation: { type: 'string', example: 'grpc://gitlab.example.com:8150' } + expose :version, documentation: { type: 'string', example: '15.0.0' } end end end diff --git a/lib/api/metadata.rb b/lib/api/metadata.rb index 3e42ffe336a..2fdb97f98ef 100644 --- a/lib/api/metadata.rb +++ b/lib/api/metadata.rb @@ -9,6 +9,8 @@ module API before { authenticate! } + METADATA_TAGS = %w[metadata].freeze + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned METADATA_QUERY = <<~EOF @@ -35,30 +37,13 @@ module API end end - desc 'Retrieve metadata information for this GitLab instance.' do + desc 'Retrieve metadata information for this GitLab instance' do detail 'This feature was introduced in GitLab 15.2.' - success [ - { - code: 200, - model: Entities::Metadata, - message: 'successful operation', - examples: { - successful_response: { - 'value' => { - version: "15.0-pre", - revision: "c401a659d0c", - kas: { - enabled: true, - externalUrl: "grpc://gitlab.example.com:8150", - version: "15.0.0" - } - } - } - } - } + success Entities::Metadata + failure [ + { code: 401, message: 'Unauthorized' } ] - failure [{ code: 401, message: 'unauthorized operation' }] - tags %w[metadata] + tags METADATA_TAGS end get '/metadata' do run_metadata_query @@ -66,31 +51,14 @@ module API # Support the deprecated `/version` route. # See https://gitlab.com/gitlab-org/gitlab/-/issues/366287 - desc 'Get the version information of the GitLab instance.' do + desc 'Retrieves version information for the GitLab instance' do detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \ 'We recommend you instead use the Metadata API.' - success [ - { - code: 200, - model: Entities::Metadata, - message: 'successful operation', - examples: { - 'Example' => { - 'value' => { - version: "15.0-pre", - revision: "c401a659d0c", - kas: { - enabled: true, - externalUrl: "grpc://gitlab.example.com:8150", - version: "15.0.0" - } - } - } - } - } + success Entities::Metadata + failure [ + { code: 401, message: 'Unauthorized' } ] - failure [{ code: 401, message: 'unauthorized operation' }] - tags %w[metadata] + tags METADATA_TAGS end get '/version' do diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index c86b7785ce2..e4a26838746 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -58,7 +58,7 @@ module API project = find_project!( ::Gitlab::Jira::Dvcs.restore_full_path(**params.slice(:namespace, :project).symbolize_keys) ) - not_found! unless can?(current_user, :download_code, project) + not_found! unless can?(current_user, :read_code, project) project end diff --git a/lib/gitlab/cluster/puma_worker_killer_initializer.rb b/lib/gitlab/cluster/puma_worker_killer_initializer.rb index 5908de68687..957faf797b5 100644 --- a/lib/gitlab/cluster/puma_worker_killer_initializer.rb +++ b/lib/gitlab/cluster/puma_worker_killer_initializer.rb @@ -9,6 +9,10 @@ module Gitlab puma_master_max_memory_mb: 950, additional_puma_dev_max_memory_mb: 200) + # We are replacing PWK with Watchdog by using backward compatible RssMemoryLimit monitor by default. + # https://gitlab.com/groups/gitlab-org/-/epics/9119 + return if Gitlab::Utils.to_boolean(ENV.fetch('GITLAB_MEMORY_WATCHDOG_ENABLED', true)) + require 'puma_worker_killer' PumaWorkerKiller.config do |config| diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml index f9a7ac017ce..bf6ebb21f7d 100644 --- a/lib/gitlab/database/gitlab_schemas.yml +++ b/lib/gitlab/database/gitlab_schemas.yml @@ -168,6 +168,7 @@ dast_site_profiles_pipelines: :gitlab_main dast_sites: :gitlab_main dast_site_tokens: :gitlab_main dast_site_validations: :gitlab_main +dependency_proxy_blob_states: :gitlab_main dependency_proxy_blobs: :gitlab_main dependency_proxy_group_settings: :gitlab_main dependency_proxy_image_ttl_group_policies: :gitlab_main diff --git a/lib/gitlab/database/load_balancing/load_balancer.rb b/lib/gitlab/database/load_balancing/load_balancer.rb index 0881025b425..cb3a378ad64 100644 --- a/lib/gitlab/database/load_balancing/load_balancer.rb +++ b/lib/gitlab/database/load_balancing/load_balancer.rb @@ -119,6 +119,13 @@ module Gitlab connection = pool.connection transaction_open = connection.transaction_open? + if attempt && attempt > 1 + ::Gitlab::Database::LoadBalancing::Logger.warn( + event: :read_write_retry, + message: 'A read_write block was retried because of connection error' + ) + end + yield connection rescue StandardError => e # No leaking will happen on the final attempt. Leaks are caused by subsequent retries diff --git a/lib/gitlab/github_import/importer/events/changed_label.rb b/lib/gitlab/github_import/importer/events/changed_label.rb index 83130d18db9..553ef0886e8 100644 --- a/lib/gitlab/github_import/importer/events/changed_label.rb +++ b/lib/gitlab/github_import/importer/events/changed_label.rb @@ -13,6 +13,7 @@ module Gitlab def create_event(issue_event) attrs = { + importing: true, user_id: author_id(issue_event), label_id: label_finder.id_for(issue_event.label_title), action: action(issue_event.event), diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index a2d06b7f5b3..a42cac61a55 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -44,30 +44,30 @@ module Gitlab TRANSLATION_LEVELS = { 'bg' => 0, 'cs_CZ' => 0, - 'da_DK' => 37, + 'da_DK' => 36, 'de' => 17, 'en' => 100, 'eo' => 0, - 'es' => 36, + 'es' => 35, 'fil_PH' => 0, - 'fr' => 72, + 'fr' => 85, 'gl_ES' => 0, 'id_ID' => 0, 'it' => 1, - 'ja' => 31, - 'ko' => 20, + 'ja' => 30, + 'ko' => 21, 'nb_NO' => 25, 'nl_NL' => 0, 'pl_PL' => 3, - 'pt_BR' => 57, - 'ro_RO' => 99, - 'ru' => 26, + 'pt_BR' => 58, + 'ro_RO' => 98, + 'ru' => 25, 'si_LK' => 11, 'tr_TR' => 11, - 'uk' => 49, + 'uk' => 52, 'zh_CN' => 98, 'zh_HK' => 1, - 'zh_TW' => 99 + 'zh_TW' => 100 }.freeze private_constant :TRANSLATION_LEVELS diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 964fba75001..64fe7b182e2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -343,6 +343,11 @@ msgid_plural "%d more comments" msgstr[0] "" msgstr[1] "" +msgid "%d package" +msgid_plural "%d packages" +msgstr[0] "" +msgstr[1] "" + msgid "%d pending comment" msgid_plural "%d pending comments" msgstr[0] "" @@ -7198,6 +7203,9 @@ msgstr "" msgid "BuildArtifacts|Loading artifacts" msgstr "" +msgid "Building your merge request. Wait a few moments, then refresh this page." +msgstr "" + msgid "Built-in" msgstr "" @@ -28913,6 +28921,9 @@ msgstr "" msgid "PackageRegistry|Delete package version" msgstr "" +msgid "PackageRegistry|Delete packages" +msgstr "" + msgid "PackageRegistry|Delete selected" msgstr "" @@ -29047,6 +29058,9 @@ msgstr[1] "" msgid "PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}" msgstr "" +msgid "PackageRegistry|Packages deleted successfully" +msgstr "" + msgid "PackageRegistry|Permanently delete" msgstr "" @@ -29101,6 +29115,9 @@ msgstr "" msgid "PackageRegistry|Show Yarn commands" msgstr "" +msgid "PackageRegistry|Something went wrong while deleting packages." +msgstr "" + msgid "PackageRegistry|Something went wrong while deleting the package asset." msgstr "" @@ -29172,6 +29189,11 @@ msgid_plural "PackageRegistry|You are about to delete %d assets. This operation msgstr[0] "" msgstr[1] "" +msgid "PackageRegistry|You are about to delete 1 package. This operation is irreversible." +msgid_plural "PackageRegistry|You are about to delete %d packages. This operation is irreversible." +msgstr[0] "" +msgstr[1] "" + msgid "PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?" msgstr "" diff --git a/package.json b/package.json index a4aba6591f2..0fe20b96301 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "dateformat": "^5.0.1", "deckar01-task_list": "^2.3.1", "diff": "^3.4.0", - "dompurify": "^2.4.0", + "dompurify": "^2.4.1", "dropzone": "^4.2.0", "editorconfig": "^0.15.3", "emoji-regex": "^10.0.0", diff --git a/qa/Dockerfile b/qa/Dockerfile index 58839dbff79..7f236a25288 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,9 +1,9 @@ ARG DOCKER_VERSION=20.10.14 -ARG CHROME_VERSION=103 +ARG CHROME_VERSION=106 ARG QA_BUILD_TARGET=qa ARG RUBY_VERSION=2.7 -FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.33-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS qa +FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS qa LABEL maintainer="GitLab Quality Department <quality@gitlab.com>" ENV DEBIAN_FRONTEND="noninteractive" diff --git a/spec/factories/dependency_proxy.rb b/spec/factories/dependency_proxy.rb index afa6c61116a..33356a701df 100644 --- a/spec/factories/dependency_proxy.rb +++ b/spec/factories/dependency_proxy.rb @@ -4,13 +4,20 @@ FactoryBot.define do factory :dependency_proxy_blob, class: 'DependencyProxy::Blob' do group size { 1234 } - file { fixture_file_upload('spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') } file_name { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz' } status { :default } + after(:build) do |blob, _evaluator| + blob.file = fixture_file_upload('spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') + end + trait :pending_destruction do status { :pending_destruction } end + + trait :remote_store do + file_store { DependencyProxy::FileUploader::Store::REMOTE } + end end factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index 666bf3594de..2e63ec2d4f2 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -2,14 +2,13 @@ require 'spec_helper' -RSpec.describe 'Global search' do +RSpec.describe 'Global search', :js do include AfterNextHelpers - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } before do - stub_feature_flags(search_page_vertical_nav: false) project.add_maintainer(user) sign_in(user) end @@ -42,7 +41,7 @@ RSpec.describe 'Global search' do end end - it 'closes the dropdown on blur', :js do + it 'closes the dropdown on blur' do find('#search').click fill_in 'search', with: "a" @@ -59,7 +58,7 @@ RSpec.describe 'Global search' do expect(page).to have_no_selector('#js-header-search') end - it 'focuses search input when shortcut "s" is pressed', :js do + it 'focuses search input when shortcut "s" is pressed' do expect(page).not_to have_selector('#search:focus') find('body').native.send_key('s') @@ -74,7 +73,7 @@ RSpec.describe 'Global search' do stub_feature_flags(new_header_search: true) visit dashboard_projects_path - # intialize javascript loaded input search input field + # initialize javascript loaded input search input field find('#search').click find('body').click end @@ -84,7 +83,7 @@ RSpec.describe 'Global search' do expect(page).to have_selector('#js-header-search') end - it 'focuses search input when shortcut "s" is pressed', :js do + it 'focuses search input when shortcut "s" is pressed' do expect(page).not_to have_selector('#search:focus') find('body').native.send_key('s') diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 2e65183d26f..0bae019793c 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -38,6 +38,20 @@ RSpec.describe 'Merge request > User sees diff', :js do end end + context 'when linking to a line' do + let(:note) { create :diff_note_on_merge_request, project: project, noteable: merge_request } + let(:line) { note.diff_file.highlighted_diff_lines.last } + let(:line_code) { line.line_code } + + before do + visit "#{diffs_project_merge_request_path(project, merge_request)}##{line_code}" + end + + it 'shows the linked line' do + expect(page).to have_selector("[id='#{line_code}']", visible: true, obscured: false) + end + end + context 'when merge request has overflow' do it 'displays warning' do allow(Commit).to receive(:max_diff_options).and_return(max_files: 3) diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb index 69b9a0aa64d..d18729d080a 100644 --- a/spec/features/snippets/search_snippets_spec.rb +++ b/spec/features/snippets/search_snippets_spec.rb @@ -2,11 +2,7 @@ require 'spec_helper' -RSpec.describe 'Search Snippets' do - before do - stub_feature_flags(search_page_vertical_nav: false) - end - +RSpec.describe 'Search Snippets', :js do it 'user searches for snippets by title' do public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle') private_snippet = create(:personal_snippet, :private, title: 'Middle and End') diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index bf75f956d7f..87366cdbfc5 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -183,11 +183,11 @@ describe('DiffsStoreActions', () => { beforeEach(() => { delete noFilesData.diff_files; - - mock.onGet(endpointMetadata).reply(200, diffMetadata); }); it('should fetch diff meta information', () => { + mock.onGet(endpointMetadata).reply(200, diffMetadata); + return testAction( diffActions.fetchDiffFilesMeta, {}, @@ -206,6 +206,40 @@ describe('DiffsStoreActions', () => { [], ); }); + + it('should show a warning on 404 reponse', async () => { + mock.onGet(endpointMetadata).reply(404); + + await testAction( + diffActions.fetchDiffFilesMeta, + {}, + { endpointMetadata, diffViewType: 'inline', showWhitespace: true }, + [{ type: types.SET_LOADING, payload: true }], + [], + ); + + expect(createAlert).toHaveBeenCalledTimes(1); + expect(createAlert).toHaveBeenCalledWith({ + message: expect.stringMatching( + 'Building your merge request. Wait a few moments, then refresh this page.', + ), + variant: 'warning', + }); + }); + + it('should show no warning on any other status code', async () => { + mock.onGet(endpointMetadata).reply(500); + + await testAction( + diffActions.fetchDiffFilesMeta, + {}, + { endpointMetadata, diffViewType: 'inline', showWhitespace: true }, + [{ type: types.SET_LOADING, payload: true }], + [], + ); + + expect(createAlert).not.toHaveBeenCalled(); + }); }); describe('fetchCoverageFiles', () => { diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js index 0eb08f0cf55..32126a5fd9a 100644 --- a/spec/frontend/editor/schema/ci/ci_schema_spec.js +++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js @@ -18,9 +18,7 @@ import VariablesJson from './json_tests/positive_tests/variables.json'; import DefaultNoAdditionalPropertiesJson from './json_tests/negative_tests/default_no_additional_properties.json'; import InheritDefaultNoAdditionalPropertiesJson from './json_tests/negative_tests/inherit_default_no_additional_properties.json'; import JobVariablesMustNotContainObjectsJson from './json_tests/negative_tests/job_variables_must_not_contain_objects.json'; -import ReleaseAssetsLinksEmptyJson from './json_tests/negative_tests/release_assets_links_empty.json'; -import ReleaseAssetsLinksInvalidLinkTypeJson from './json_tests/negative_tests/release_assets_links_invalid_link_type.json'; -import ReleaseAssetsLinksMissingJson from './json_tests/negative_tests/release_assets_links_missing.json'; +import ReleaseAssetsLinksJson from './json_tests/negative_tests/release_assets_links.json'; import RetryUnknownWhenJson from './json_tests/negative_tests/retry_unknown_when.json'; // YAML POSITIVE TEST @@ -35,38 +33,18 @@ import JobWhenYaml from './yaml_tests/positive_tests/job_when.yml'; // YAML NEGATIVE TEST import ArtifactsNegativeYaml from './yaml_tests/negative_tests/artifacts.yml'; +import CacheKeyNeative from './yaml_tests/negative_tests/cache.yml'; import IncludeNegativeYaml from './yaml_tests/negative_tests/include.yml'; -import RulesNegativeYaml from './yaml_tests/negative_tests/rules.yml'; -import VariablesInvalidSyntaxDescYaml from './yaml_tests/negative_tests/variables/invalid_syntax_desc.yml'; -import VariablesWrongSyntaxUsageExpand from './yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml'; import JobWhenNegativeYaml from './yaml_tests/negative_tests/job_when.yml'; - import ProjectPathIncludeEmptyYaml from './yaml_tests/negative_tests/project_path/include/empty.yml'; import ProjectPathIncludeInvalidVariableYaml from './yaml_tests/negative_tests/project_path/include/invalid_variable.yml'; import ProjectPathIncludeLeadSlashYaml from './yaml_tests/negative_tests/project_path/include/leading_slash.yml'; import ProjectPathIncludeNoSlashYaml from './yaml_tests/negative_tests/project_path/include/no_slash.yml'; import ProjectPathIncludeTailSlashYaml from './yaml_tests/negative_tests/project_path/include/tailing_slash.yml'; -import ProjectPathTriggerIncludeYaml from './yaml_tests/negative_tests/project_path/trigger/trigger_include.yml'; -import ProjectPathTriggerMinimalEmptyYaml from './yaml_tests/negative_tests/project_path/trigger/minimal/empty.yml'; -import ProjectPathTriggerMinimalInvalidVariableYaml from './yaml_tests/negative_tests/project_path/trigger/minimal/invalid_variable.yml'; -import ProjectPathTriggerMinimalLeadSlashYaml from './yaml_tests/negative_tests/project_path/trigger/minimal/leading_slash.yml'; -import ProjectPathTriggerMinimalNoSlashYaml from './yaml_tests/negative_tests/project_path/trigger/minimal/no_slash.yml'; -import ProjectPathTriggerMinimalTailSlashYaml from './yaml_tests/negative_tests/project_path/trigger/minimal/tailing_slash.yml'; -import ProjectPathTriggerProjectEmptyYaml from './yaml_tests/negative_tests/project_path/trigger/project/empty.yml'; -import ProjectPathTriggerProjectInvalidVariableYaml from './yaml_tests/negative_tests/project_path/trigger/project/invalid_variable.yml'; -import ProjectPathTriggerProjectLeadSlashYaml from './yaml_tests/negative_tests/project_path/trigger/project/leading_slash.yml'; -import ProjectPathTriggerProjectNoSlashYaml from './yaml_tests/negative_tests/project_path/trigger/project/no_slash.yml'; -import ProjectPathTriggerProjectTailSlashYaml from './yaml_tests/negative_tests/project_path/trigger/project/tailing_slash.yml'; - -import CacheKeyFilesNotArray from './yaml_tests/negative_tests/cache/key_files_not_an_array.yml'; -import CacheKeyPrefixArray from './yaml_tests/negative_tests/cache/key_prefix_array.yml'; -import CacheKeyWithDot from './yaml_tests/negative_tests/cache/key_with_dot.yml'; -import CacheKeyWithMultipleDots from './yaml_tests/negative_tests/cache/key_with_multiple_dots.yml'; -import CacheKeyWithSlash from './yaml_tests/negative_tests/cache/key_with_slash.yml'; -import CachePathsNotAnArray from './yaml_tests/negative_tests/cache/paths_not_an_array.yml'; -import CacheUntrackedString from './yaml_tests/negative_tests/cache/untracked_string.yml'; -import CacheWhenInteger from './yaml_tests/negative_tests/cache/when_integer.yml'; -import CacheWhenNotReservedKeyword from './yaml_tests/negative_tests/cache/when_not_reserved_keyword.yml'; +import RulesNegativeYaml from './yaml_tests/negative_tests/rules.yml'; +import TriggerNegative from './yaml_tests/negative_tests/trigger.yml'; +import VariablesInvalidSyntaxDescYaml from './yaml_tests/negative_tests/variables/invalid_syntax_desc.yml'; +import VariablesWrongSyntaxUsageExpand from './yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml'; const ajv = new Ajv({ strictTypes: false, @@ -119,22 +97,12 @@ describe('negative tests', () => { DefaultNoAdditionalPropertiesJson, JobVariablesMustNotContainObjectsJson, InheritDefaultNoAdditionalPropertiesJson, - ReleaseAssetsLinksEmptyJson, - ReleaseAssetsLinksInvalidLinkTypeJson, - ReleaseAssetsLinksMissingJson, + ReleaseAssetsLinksJson, RetryUnknownWhenJson, // YAML ArtifactsNegativeYaml, - CacheKeyFilesNotArray, - CacheKeyPrefixArray, - CacheKeyWithDot, - CacheKeyWithMultipleDots, - CacheKeyWithSlash, - CachePathsNotAnArray, - CacheUntrackedString, - CacheWhenInteger, - CacheWhenNotReservedKeyword, + CacheKeyNeative, IncludeNegativeYaml, JobWhenNegativeYaml, RulesNegativeYaml, @@ -145,17 +113,7 @@ describe('negative tests', () => { ProjectPathIncludeLeadSlashYaml, ProjectPathIncludeNoSlashYaml, ProjectPathIncludeTailSlashYaml, - ProjectPathTriggerIncludeYaml, - ProjectPathTriggerMinimalEmptyYaml, - ProjectPathTriggerMinimalInvalidVariableYaml, - ProjectPathTriggerMinimalLeadSlashYaml, - ProjectPathTriggerMinimalNoSlashYaml, - ProjectPathTriggerMinimalTailSlashYaml, - ProjectPathTriggerProjectEmptyYaml, - ProjectPathTriggerProjectInvalidVariableYaml, - ProjectPathTriggerProjectLeadSlashYaml, - ProjectPathTriggerProjectNoSlashYaml, - ProjectPathTriggerProjectTailSlashYaml, + TriggerNegative, }), )('schema validates %s', (_, input) => { // We construct a new "JSON" from each main key that is inside a diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/default_no_additional_properties.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/default_no_additional_properties.json index 955c19ef1ab..d30bc4649ab 100644 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/default_no_additional_properties.json +++ b/spec/frontend/editor/schema/ci/json_tests/negative_tests/default_no_additional_properties.json @@ -9,4 +9,4 @@ "name": "test" } } -} +}
\ No newline at end of file diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/inherit_default_no_additional_properties.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/inherit_default_no_additional_properties.json index 7411e4c2434..1a31467f9ae 100644 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/inherit_default_no_additional_properties.json +++ b/spec/frontend/editor/schema/ci/json_tests/negative_tests/inherit_default_no_additional_properties.json @@ -1,8 +1,10 @@ { "karma": { "inherit": { - "default": ["secrets"] + "default": [ + "secrets" + ] }, "script": "karma" } -} +}
\ No newline at end of file diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/job_variables_must_not_contain_objects.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/job_variables_must_not_contain_objects.json index bfdbf26ee70..68dd57824ab 100644 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/job_variables_must_not_contain_objects.json +++ b/spec/frontend/editor/schema/ci/json_tests/negative_tests/job_variables_must_not_contain_objects.json @@ -1,7 +1,9 @@ { "gitlab-ci-variables-object": { "stage": "test", - "script": ["true"], + "script": [ + "true" + ], "variables": { "DEPLOY_ENVIRONMENT": { "value": "staging", @@ -9,4 +11,4 @@ } } } -} +}
\ No newline at end of file diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_invalid_link_type.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links.json index 048911aefa3..00b5b54c7e2 100644 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_invalid_link_type.json +++ b/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links.json @@ -1,4 +1,24 @@ { + "gitlab-ci-release-assets-links-missing": { + "script": "dostuff", + "stage": "deploy", + "release": { + "description": "Created using the release-cli $EXTRA_DESCRIPTION", + "tag_name": "$CI_COMMIT_TAG", + "assets": {} + } + }, + "gitlab-ci-release-assets-links-empty": { + "script": "dostuff", + "stage": "deploy", + "release": { + "description": "Created using the release-cli $EXTRA_DESCRIPTION", + "tag_name": "$CI_COMMIT_TAG", + "assets": { + "links": [] + } + } + }, "gitlab-ci-release-assets-links-invalid-link-type": { "script": "dostuff", "stage": "deploy", @@ -21,4 +41,4 @@ } } } -} +}
\ No newline at end of file diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_empty.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_empty.json deleted file mode 100644 index 84a1aa14698..00000000000 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_empty.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "gitlab-ci-release-assets-links-empty": { - "script": "dostuff", - "stage": "deploy", - "release": { - "description": "Created using the release-cli $EXTRA_DESCRIPTION", - "tag_name": "$CI_COMMIT_TAG", - "assets": { - "links": [] - } - } - } -} diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_missing.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_missing.json deleted file mode 100644 index 6f0b5a3bff8..00000000000 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/release_assets_links_missing.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "gitlab-ci-release-assets-links-missing": { - "script": "dostuff", - "stage": "deploy", - "release": { - "description": "Created using the release-cli $EXTRA_DESCRIPTION", - "tag_name": "$CI_COMMIT_TAG", - "assets": {} - } - } -} diff --git a/spec/frontend/editor/schema/ci/json_tests/negative_tests/retry_unknown_when.json b/spec/frontend/editor/schema/ci/json_tests/negative_tests/retry_unknown_when.json index 433504f52c6..2c53ce07109 100644 --- a/spec/frontend/editor/schema/ci/json_tests/negative_tests/retry_unknown_when.json +++ b/spec/frontend/editor/schema/ci/json_tests/negative_tests/retry_unknown_when.json @@ -6,4 +6,4 @@ "when": "gitlab-ci-retry-object-unknown-when" } } -} +}
\ No newline at end of file diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache.yml new file mode 100644 index 00000000000..3979c9ae2ac --- /dev/null +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache.yml @@ -0,0 +1,62 @@ +cache-key-files-not-an-array: + script: echo "This job uses a cache." + cache: + key: + files: package.json + paths: + - vendor/ruby + - node_modules + +cache-key-prefix-array: + script: echo "This job uses a cache." + cache: + key: + files: + - Gemfile.lock + prefix: + - binaries-cache-$CI_JOB_NAME + paths: + - binaries/ + +cache-key-with-.: + script: echo "This job uses a cache." + cache: + key: . + paths: + - binaries/ + +cache-key-with-multiple-.: + stage: test + script: echo "This job uses a cache." + cache: + key: .. + paths: + - binaries/ + +cache-key-with-/: + script: echo "This job uses a cache." + cache: + key: binaries-ca/che + paths: + - binaries/ + +cache-path-not-an-array: + script: echo "This job uses a cache." + cache: + key: binaries-cache + paths: binaries/*.apk + +cache-untracked-string: + script: echo "This job uses a cache." + cache: + untracked: 'true' + +when_integer: + script: echo "This job uses a cache." + cache: + when: 0 + +when_not_reserved_keyword: + script: echo "This job uses a cache." + cache: + when: 'never' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_files_not_an_array.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_files_not_an_array.yml deleted file mode 100644 index 64b927a9940..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_files_not_an_array.yml +++ /dev/null @@ -1,8 +0,0 @@ -cache-key-files-not-an-array: - script: echo "This job uses a cache." - cache: - key: - files: package.json - paths: - - vendor/ruby - - node_modules diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_prefix_array.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_prefix_array.yml deleted file mode 100644 index 9024dfe6441..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_prefix_array.yml +++ /dev/null @@ -1,10 +0,0 @@ -cache-key-prefix-array: - script: echo "This job uses a cache." - cache: - key: - files: - - Gemfile.lock - prefix: - - binaries-cache-$CI_JOB_NAME - paths: - - binaries/ diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_dot.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_dot.yml deleted file mode 100644 index 7d21e5f4111..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_dot.yml +++ /dev/null @@ -1,6 +0,0 @@ -cache-key-with-.: - script: echo "This job uses a cache." - cache: - key: . - paths: - - binaries/ diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_multiple_dots.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_multiple_dots.yml deleted file mode 100644 index 1256be628d1..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_multiple_dots.yml +++ /dev/null @@ -1,7 +0,0 @@ -cache-key-with-multiple-.: - stage: test - script: echo "This job uses a cache." - cache: - key: .. - paths: - - binaries/ diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_slash.yml deleted file mode 100644 index ea6c0345bd4..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/key_with_slash.yml +++ /dev/null @@ -1,6 +0,0 @@ -cache-key-with-/: - script: echo "This job uses a cache." - cache: - key: binaries-ca/che - paths: - - binaries/ diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/paths_not_an_array.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/paths_not_an_array.yml deleted file mode 100644 index 26cc8d1935e..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/paths_not_an_array.yml +++ /dev/null @@ -1,5 +0,0 @@ -cache-path-not-an-array: - script: echo "This job uses a cache." - cache: - key: binaries-cache - paths: binaries/*.apk diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/untracked_string.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/untracked_string.yml deleted file mode 100644 index ed21e87f009..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/untracked_string.yml +++ /dev/null @@ -1,4 +0,0 @@ -cache-untracked-string: - script: echo "This job uses a cache." - cache: - untracked: 'true' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/when_integer.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/when_integer.yml deleted file mode 100644 index 5420bd9d0dd..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/when_integer.yml +++ /dev/null @@ -1,4 +0,0 @@ -when_integer: - script: echo "This job uses a cache." - cache: - when: 0 diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/when_not_reserved_keyword.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/when_not_reserved_keyword.yml deleted file mode 100644 index 2a6e204a6db..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/cache/when_not_reserved_keyword.yml +++ /dev/null @@ -1,4 +0,0 @@ -when_not_reserved_keyword: - script: echo "This job uses a cache." - cache: - when: 'never' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/empty.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/empty.yml deleted file mode 100644 index cad8dbbf430..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/empty.yml +++ /dev/null @@ -1,2 +0,0 @@ -trigger-minimal: - trigger: '' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/invalid_variable.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/invalid_variable.yml deleted file mode 100644 index 6ca37666d09..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/invalid_variable.yml +++ /dev/null @@ -1,2 +0,0 @@ -trigger-minimal: - trigger: 'slug#' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/leading_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/leading_slash.yml deleted file mode 100644 index 9d7c6b44125..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/leading_slash.yml +++ /dev/null @@ -1,2 +0,0 @@ -trigger-minimal: - trigger: '/slug' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/no_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/no_slash.yml deleted file mode 100644 index acd047477c8..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/no_slash.yml +++ /dev/null @@ -1,2 +0,0 @@ -trigger-minimal: - trigger: 'slug' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/tailing_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/tailing_slash.yml deleted file mode 100644 index 0fdd00da3de..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/minimal/tailing_slash.yml +++ /dev/null @@ -1,2 +0,0 @@ -trigger-minimal: - trigger: 'slug/' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/empty.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/empty.yml deleted file mode 100644 index 0aa2330cecb..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/empty.yml +++ /dev/null @@ -1,3 +0,0 @@ -trigger-project: - trigger: - project: '' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/invalid_variable.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/invalid_variable.yml deleted file mode 100644 index 3c17ec62039..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/invalid_variable.yml +++ /dev/null @@ -1,3 +0,0 @@ -trigger-project: - trigger: - project: 'slug#' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/leading_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/leading_slash.yml deleted file mode 100644 index f9884603171..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/leading_slash.yml +++ /dev/null @@ -1,3 +0,0 @@ -trigger-project: - trigger: - project: '/slug' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/no_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/no_slash.yml deleted file mode 100644 index d89e09756eb..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/no_slash.yml +++ /dev/null @@ -1,3 +0,0 @@ -trigger-project: - trigger: - project: 'slug' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/tailing_slash.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/tailing_slash.yml deleted file mode 100644 index 3c39d6be4cb..00000000000 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/project/tailing_slash.yml +++ /dev/null @@ -1,3 +0,0 @@ -trigger-project: - trigger: - project: 'slug/' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/trigger_include.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/trigger.yml index 6527db04a62..73cc82f2f1c 100644 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/project_path/trigger/trigger_include.yml +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/trigger.yml @@ -1,3 +1,38 @@ +trigger-minimal-empty: + trigger: '' + +trigger-minimal-invalid-variable: + trigger: 'slug#' + +trigger-minimal-leading-slash: + trigger: '/slug' + +trigger-minimal-no-slash: + trigger: 'slug' + +trigger-minimal-trailing-slash: + trigger: 'slug/' + +trigger-project-empty: + trigger: + project: '' + +trigger-project-invalid-variable: + trigger: + project: 'slug#' + +trigger-project-leading-slash: + trigger: + project: '/slug' + +trigger-project-no-slash: + trigger: + project: 'slug' + +trigger-project-trailing-slash: + trigger: + project: 'slug/' + trigger-include-empty: trigger: include: diff --git a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js new file mode 100644 index 00000000000..e0e26434680 --- /dev/null +++ b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js @@ -0,0 +1,71 @@ +import { GlModal as RealGlModal } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; +import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; + +const GlModal = stubComponent(RealGlModal, { + methods: { + show: jest.fn(), + }, +}); + +describe('DeleteModal', () => { + let wrapper; + + const defaultItemsToBeDeleted = [ + { + name: 'package 01', + }, + { + name: 'package 02', + }, + ]; + + const findModal = () => wrapper.findComponent(GlModal); + + const mountComponent = ({ itemsToBeDeleted = defaultItemsToBeDeleted } = {}) => { + wrapper = shallowMountExtended(DeleteModal, { + propsData: { + itemsToBeDeleted, + }, + stubs: { + GlModal, + }, + }); + }; + + beforeEach(() => { + mountComponent(); + }); + + it('passes title prop', () => { + expect(findModal().props('title')).toMatchInterpolatedText('Delete packages'); + }); + + it('passes actionPrimary prop', () => { + expect(findModal().props('actionPrimary')).toStrictEqual({ + text: 'Permanently delete', + attributes: [{ variant: 'danger' }, { category: 'primary' }], + }); + }); + + it('renders description', () => { + expect(findModal().text()).toContain( + 'You are about to delete 2 packages. This operation is irreversible.', + ); + }); + + it('emits confirm when primary event is emitted', () => { + expect(wrapper.emitted('confirm')).toBeUndefined(); + + findModal().vm.$emit('primary'); + + expect(wrapper.emitted('confirm')).toHaveLength(1); + }); + + it('show calls gl-modal show', () => { + findModal().vm.show(); + + expect(GlModal.methods.show).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap index 5be05ddf629..a7de751aadd 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap @@ -8,7 +8,14 @@ exports[`packages_list_row renders 1`] = ` <div class="gl-display-flex gl-align-items-center gl-py-3" > - <!----> + <div + class="gl-w-7 gl-display-flex gl-justify-content-start gl-pl-2" + > + <gl-form-checkbox-stub + class="gl-m-0" + id="2" + /> + </div> <div class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-grow-1" diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js index b5a512b8806..913b4f5926f 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js @@ -1,4 +1,4 @@ -import { GlSprintf } from '@gitlab/ui'; +import { GlFormCheckbox, GlSprintf } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueRouter from 'vue-router'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -40,9 +40,11 @@ describe('packages_list_row', () => { const findPublishMethod = () => wrapper.findComponent(PublishMethod); const findCreatedDateText = () => wrapper.findByTestId('created-date'); const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip); + const findBulkDeleteAction = () => wrapper.findComponent(GlFormCheckbox); const mountComponent = ({ packageEntity = packageWithoutTags, + selected = false, provide = defaultProvide, } = {}) => { wrapper = shallowMountExtended(PackagesListRow, { @@ -53,6 +55,7 @@ describe('packages_list_row', () => { }, propsData: { packageEntity, + selected, }, directives: { GlTooltip: createMockDirective(), @@ -117,14 +120,13 @@ describe('packages_list_row', () => { }); }); - it('emits the packageToDelete event when the delete button is clicked', async () => { + it('emits the delete event when the delete button is clicked', async () => { mountComponent({ packageEntity: packageWithoutTags }); findDeleteDropdown().vm.$emit('click'); await nextTick(); - expect(wrapper.emitted('packageToDelete')).toHaveLength(1); - expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]); + expect(wrapper.emitted('delete')).toHaveLength(1); }); }); @@ -151,6 +153,39 @@ describe('packages_list_row', () => { }); }); + describe('left action template', () => { + it('does not render checkbox if not permitted', () => { + mountComponent({ + packageEntity: { ...packageWithoutTags, canDestroy: false }, + }); + + expect(findBulkDeleteAction().exists()).toBe(false); + }); + + it('renders checkbox', () => { + mountComponent(); + + expect(findBulkDeleteAction().exists()).toBe(true); + expect(findBulkDeleteAction().attributes('checked')).toBeUndefined(); + }); + + it('emits select when checked', () => { + mountComponent(); + + findBulkDeleteAction().vm.$emit('change'); + + expect(wrapper.emitted('select')).toHaveLength(1); + }); + + it('renders checkbox in selected state if selected', () => { + mountComponent({ + selected: true, + }); + + expect(findBulkDeleteAction().attributes('checked')).toBe('true'); + }); + }); + describe('secondary left info', () => { it('has the package version', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js index c5b6b7da65b..7cc5bea0f7a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js @@ -99,10 +99,10 @@ describe('packages_list', () => { it('shows the registry list with the right props', () => { expect(findRegistryList().props()).toMatchObject({ + title: '2 packages', items: defaultProps.list, pagination: defaultProps.pageInfo, isLoading: false, - hiddenDelete: true, }); }); @@ -128,7 +128,7 @@ describe('packages_list', () => { describe('when the user can destroy the package', () => { beforeEach(async () => { mountComponent(); - await findPackagesListRow().vm.$emit('packageToDelete', firstPackage); + await findPackagesListRow().vm.$emit('delete', firstPackage); }); it('passes itemToBeDeleted to the modal', () => { @@ -148,6 +148,27 @@ describe('packages_list', () => { }); }); + describe('when the user can bulk destroy packages', () => { + beforeEach(() => { + mountComponent(); + }); + + it('passes itemToBeDeleted to the modal when there is only one package', async () => { + await findRegistryList().vm.$emit('delete', [firstPackage]); + + expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(firstPackage); + expect(wrapper.emitted('delete')).toBeUndefined(); + }); + + it('emits delete when there is more than one package', () => { + const items = [firstPackage, secondPackage]; + findRegistryList().vm.$emit('delete', items); + + expect(wrapper.emitted('delete')).toHaveLength(1); + expect(wrapper.emitted('delete')[0]).toEqual([items]); + }); + }); + describe('when an error package is present', () => { beforeEach(() => { mountComponent({ list: [firstPackage, errorPackage] }); @@ -210,7 +231,7 @@ describe('packages_list', () => { beforeEach(() => { eventSpy = jest.spyOn(Tracking, 'event'); mountComponent(); - findPackagesListRow().vm.$emit('packageToDelete', firstPackage); + findPackagesListRow().vm.$emit('delete', firstPackage); return nextTick(); }); diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js index f247c83c85f..f36c5923532 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -294,6 +294,33 @@ export const packageDestroyMutation = () => ({ }, }); +export const packagesDestroyMutation = () => ({ + data: { + destroyPackages: { + errors: [], + }, + }, +}); + +export const packagesDestroyMutationError = () => ({ + data: { + destroyPackages: null, + }, + errors: [ + { + message: + "The resource that you are attempting to access does not exist or you don't have permission to perform this action", + locations: [ + { + line: 2, + column: 3, + }, + ], + path: ['destroyPackages'], + }, + ], +}); + export const packageDestroyMutationError = () => ({ data: { destroyPackage: null, @@ -320,6 +347,7 @@ export const packageDestroyFilesMutation = () => ({ }, }, }); + export const packageDestroyFilesMutationError = () => ({ data: { destroyPackageFiles: null, diff --git a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap index 7759c366796..c2fecf87428 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap @@ -2,6 +2,8 @@ exports[`PackagesListApp renders 1`] = ` <div> + <!----> + <gl-card-stub bodyclass="gl-display-flex gl-p-0!" class="gl-px-8 gl-py-6 gl-line-height-20 gl-mt-3" @@ -53,7 +55,9 @@ exports[`PackagesListApp renders 1`] = ` helpurl="/help/user/packages/index" /> - <package-search-stub /> + <package-search-stub + class="gl-mb-5" + /> <div> <section @@ -115,5 +119,7 @@ exports[`PackagesListApp renders 1`] = ` </div> </section> </div> + + <div /> </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js index 942eb0b3980..abdb875e839 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js @@ -1,4 +1,4 @@ -import { GlBanner, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlAlert, GlBanner, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -6,12 +6,13 @@ import * as utils from '~/lib/utils/common_utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { stubComponent } from 'helpers/stub_component'; import ListPage from '~/packages_and_registries/package_registry/pages/list.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue'; import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue'; import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue'; import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; - +import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; import { PROJECT_RESOURCE_TYPE, GROUP_RESOURCE_TYPE, @@ -19,11 +20,19 @@ import { HIDE_PACKAGE_MIGRATION_SURVEY_COOKIE, EMPTY_LIST_HELP_URL, PACKAGE_HELP_URL, + DELETE_PACKAGES_ERROR_MESSAGE, + DELETE_PACKAGES_SUCCESS_MESSAGE, } from '~/packages_and_registries/package_registry/constants'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; - -import { packagesListQuery, packageData, pagination } from '../mock_data'; +import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; +import { + packagesListQuery, + packageData, + pagination, + packagesDestroyMutation, + packagesDestroyMutationError, +} from '../mock_data'; jest.mock('~/flash'); @@ -49,20 +58,26 @@ describe('PackagesListApp', () => { filters: { packageName: 'foo', packageType: 'CONAN' }, }; + const findAlert = () => wrapper.findComponent(GlAlert); const findBanner = () => wrapper.findComponent(GlBanner); const findPackageTitle = () => wrapper.findComponent(PackageTitle); const findSearch = () => wrapper.findComponent(PackageSearch); const findListComponent = () => wrapper.findComponent(PackageList); const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findDeletePackage = () => wrapper.findComponent(DeletePackage); + const findDeletePackagesModal = () => wrapper.findComponent(DeleteModal); const mountComponent = ({ resolver = jest.fn().mockResolvedValue(packagesListQuery()), + mutationResolver, provide = defaultProvide, } = {}) => { Vue.use(VueApollo); - const requestHandlers = [[getPackagesQuery, resolver]]; + const requestHandlers = [ + [getPackagesQuery, resolver], + [destroyPackagesMutation, mutationResolver], + ]; apolloProvider = createMockApollo(requestHandlers); wrapper = shallowMountExtended(ListPage, { @@ -76,6 +91,11 @@ describe('PackagesListApp', () => { GlLink, PackageList, DeletePackage, + DeleteModal: stubComponent(DeleteModal, { + methods: { + show: jest.fn(), + }, + }), }, }); }; @@ -348,4 +368,62 @@ describe('PackagesListApp', () => { expect(findListComponent().props('isLoading')).toBe(false); }); }); + + describe('bulk delete package', () => { + const items = [{ id: '1' }, { id: '2' }]; + + it('deletePackage is bound to package-list package:delete event', async () => { + mountComponent(); + + await waitForFirstRequest(); + + findListComponent().vm.$emit('delete', [{ id: '1' }, { id: '2' }]); + + await waitForPromises(); + + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual(items); + }); + + it('calls mutation with the right values and shows success alert', async () => { + const mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation()); + mountComponent({ + mutationResolver, + }); + + await waitForFirstRequest(); + + findListComponent().vm.$emit('delete', items); + + findDeletePackagesModal().vm.$emit('confirm'); + + expect(mutationResolver).toHaveBeenCalledWith({ + ids: items.map((item) => item.id), + }); + + await waitForPromises(); + + expect(findAlert().exists()).toBe(true); + expect(findAlert().props('variant')).toEqual('success'); + expect(findAlert().text()).toMatchInterpolatedText(DELETE_PACKAGES_SUCCESS_MESSAGE); + }); + + it('on error shows danger alert', async () => { + const mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutationError()); + mountComponent({ + mutationResolver, + }); + + await waitForFirstRequest(); + + findListComponent().vm.$emit('delete', items); + + findDeletePackagesModal().vm.$emit('confirm'); + + await waitForPromises(); + + expect(findAlert().exists()).toBe(true); + expect(findAlert().props('variant')).toEqual('danger'); + expect(findAlert().text()).toMatchInterpolatedText(DELETE_PACKAGES_ERROR_MESSAGE); + }); + }); }); diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb index fd0c2c46b2e..2c452410cca 100644 --- a/spec/graphql/mutations/commits/create_spec.rb +++ b/spec/graphql/mutations/commits/create_spec.rb @@ -179,7 +179,7 @@ RSpec.describe Mutations::Commits::Create do it 'returns errors' do expect(mutated_commit).to be_nil - expect(subject[:errors].to_s).to match(/3:UserCommitFiles: empty CommitMessage/) + expect(subject[:errors].to_s).to match(/empty CommitMessage/) end end diff --git a/spec/initializers/memory_watchdog_spec.rb b/spec/initializers/memory_watchdog_spec.rb index 5c3d020016a..92834c889c2 100644 --- a/spec/initializers/memory_watchdog_spec.rb +++ b/spec/initializers/memory_watchdog_spec.rb @@ -3,6 +3,24 @@ require 'fast_spec_helper' RSpec.describe 'memory watchdog' do + shared_examples 'starts configured watchdog' do |configure_monitor_method| + shared_examples 'configures and starts watchdog' do + it "correctly configures and starts watchdog", :aggregate_failures do + expect(Gitlab::Memory::Watchdog::Configurator).to receive(configure_monitor_method) + + expect(Gitlab::Memory::Watchdog).to receive(:new).and_return(watchdog) + expect(Gitlab::BackgroundTask).to receive(:new).with(watchdog).and_return(background_task) + expect(background_task).to receive(:start) + expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_yield + + run_initializer + end + end + end + + let(:watchdog) { instance_double(Gitlab::Memory::Watchdog) } + let(:background_task) { instance_double(Gitlab::BackgroundTask) } + subject(:run_initializer) do load rails_root_join('config/initializers/memory_watchdog.rb') end @@ -15,10 +33,6 @@ RSpec.describe 'memory watchdog' do end context 'when runtime is an application' do - let(:watchdog) { instance_double(Gitlab::Memory::Watchdog) } - let(:background_task) { instance_double(Gitlab::BackgroundTask) } - let(:logger) { Gitlab::AppLogger } - before do allow(Gitlab::Runtime).to receive(:application?).and_return(true) end @@ -29,21 +43,6 @@ RSpec.describe 'memory watchdog' do run_initializer end - shared_examples 'starts configured watchdog' do |configure_monitor_method| - shared_examples 'configures and starts watchdog' do - it "correctly configures and starts watchdog", :aggregate_failures do - expect(Gitlab::Memory::Watchdog::Configurator).to receive(configure_monitor_method) - - expect(Gitlab::Memory::Watchdog).to receive(:new).and_return(watchdog) - expect(Gitlab::BackgroundTask).to receive(:new).with(watchdog).and_return(background_task) - expect(background_task).to receive(:start) - expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_yield - - run_initializer - end - end - end - context 'when puma' do before do allow(Gitlab::Runtime).to receive(:puma?).and_return(true) @@ -92,10 +91,24 @@ RSpec.describe 'memory watchdog' do allow(Gitlab::Runtime).to receive(:application?).and_return(true) end - it 'does not register life-cycle hook' do - expect(Gitlab::Cluster::LifecycleEvents).not_to receive(:on_worker_start) + context 'when puma' do + before do + allow(Gitlab::Runtime).to receive(:puma?).and_return(true) + end - run_initializer + it_behaves_like 'starts configured watchdog', :configure_for_puma + end + + context 'when sidekiq' do + before do + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) + end + + it 'does not register life-cycle hook' do + expect(Gitlab::Cluster::LifecycleEvents).not_to receive(:on_worker_start) + + run_initializer + end end end end diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb new file mode 100644 index 00000000000..cb13a711857 --- /dev/null +++ b/spec/lib/gitlab/cluster/puma_worker_killer_initializer_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'puma_worker_killer' + +RSpec.describe Gitlab::Cluster::PumaWorkerKillerInitializer do + describe '.start' do + context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is false' do + before do + stub_env('GITLAB_MEMORY_WATCHDOG_ENABLED', 'false') + end + + it 'configures and start PumaWorkerKiller' do + expect(PumaWorkerKiller).to receive(:config) + expect(PumaWorkerKiller).to receive(:start) + + described_class.start({}) + end + end + + context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is not set' do + it 'configures and start PumaWorkerKiller' do + expect(PumaWorkerKiller).not_to receive(:config) + expect(PumaWorkerKiller).not_to receive(:start) + + described_class.start({}) + end + end + end +end diff --git a/spec/lib/gitlab/database/load_balancing/transaction_leaking_spec.rb b/spec/lib/gitlab/database/load_balancing/transaction_leaking_spec.rb index cd49544125d..1eb077fe6ca 100644 --- a/spec/lib/gitlab/database/load_balancing/transaction_leaking_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/transaction_leaking_spec.rb @@ -60,6 +60,7 @@ RSpec.describe 'Load balancer behavior with errors inside a transaction', :redis conn = model.connection expect(::Gitlab::Database::LoadBalancing::Logger).to receive(:warn).with(hash_including(event: :transaction_leak)) + expect(::Gitlab::Database::LoadBalancing::Logger).to receive(:warn).with(hash_including(event: :read_write_retry)) conn.transaction do expect(conn).to be_transaction_open @@ -78,6 +79,8 @@ RSpec.describe 'Load balancer behavior with errors inside a transaction', :redis expect(::Gitlab::Database::LoadBalancing::Logger) .not_to receive(:warn).with(hash_including(event: :transaction_leak)) + expect(::Gitlab::Database::LoadBalancing::Logger) + .to receive(:warn).with(hash_including(event: :read_write_retry)) expect(conn).not_to be_transaction_open @@ -109,6 +112,8 @@ RSpec.describe 'Load balancer behavior with errors inside a transaction', :redis it 'retries when not in a transaction' do expect(::Gitlab::Database::LoadBalancing::Logger) .not_to receive(:warn).with(hash_including(event: :transaction_leak)) + expect(::Gitlab::Database::LoadBalancing::Logger) + .to receive(:warn).with(hash_including(event: :read_write_retry)) expect { execute(model.connection) }.not_to raise_error end diff --git a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb index 4476b4123ee..6a409762599 100644 --- a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb +++ b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb @@ -10,7 +10,9 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do let(:client) { instance_double('Gitlab::GithubImport::Client') } let(:issuable) { create(:issue, project: project) } - let!(:label) { create(:label, project: project) } + let(:label) { create(:label, project: project) } + let(:label_title) { label.title } + let(:label_id) { label.id } let(:issue_event) do Gitlab::GithubImport::Representation::IssueEvent.from_json_hash( @@ -18,7 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do 'actor' => { 'id' => user.id, 'login' => user.username }, 'event' => event_type, 'commit_id' => nil, - 'label_title' => label.title, + 'label_title' => label_title, 'created_at' => '2022-04-26 18:30:53 UTC', 'issue' => { 'number' => issuable.iid, pull_request: issuable.is_a?(MergeRequest) } ) @@ -27,7 +29,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do let(:event_attrs) do { user_id: user.id, - label_id: label.id, + label_id: label_id, created_at: issue_event.created_at }.stringify_keys end @@ -42,7 +44,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do end before do - allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id) allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder| allow(finder).to receive(:database_id).and_return(issuable.id) end @@ -52,16 +53,35 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do end context 'with Issue' do - context 'when importing a labeled event' do - let(:event_type) { 'labeled' } - let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') } + context 'when importing event with associated label' do + before do + allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id) + end - it_behaves_like 'new event' + context 'when importing a labeled event' do + let(:event_type) { 'labeled' } + let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') } + + it_behaves_like 'new event' + end + + context 'when importing an unlabeled event' do + let(:event_type) { 'unlabeled' } + let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') } + + it_behaves_like 'new event' + end end - context 'when importing an unlabeled event' do - let(:event_type) { 'unlabeled' } - let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') } + context 'when importing event without associated label' do + before do + allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil) + end + + let(:label_title) { 'deleted_label' } + let(:label_id) { nil } + let(:event_type) { 'labeled' } + let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') } it_behaves_like 'new event' end @@ -70,16 +90,35 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do context 'with MergeRequest' do let(:issuable) { create(:merge_request, source_project: project, target_project: project) } - context 'when importing a labeled event' do - let(:event_type) { 'labeled' } - let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') } + context 'when importing event with associated label' do + before do + allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id) + end - it_behaves_like 'new event' + context 'when importing a labeled event' do + let(:event_type) { 'labeled' } + let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') } + + it_behaves_like 'new event' + end + + context 'when importing an unlabeled event' do + let(:event_type) { 'unlabeled' } + let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') } + + it_behaves_like 'new event' + end end - context 'when importing an unlabeled event' do - let(:event_type) { 'unlabeled' } - let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') } + context 'when importing event without associated label' do + before do + allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil) + end + + let(:label_title) { 'deleted_label' } + let(:label_id) { nil } + let(:event_type) { 'labeled' } + let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') } it_behaves_like 'new event' end diff --git a/spec/lib/gitlab/memory/watchdog/configurator_spec.rb b/spec/lib/gitlab/memory/watchdog/configurator_spec.rb index 2c5fae5736d..e6f2d57e9e6 100644 --- a/spec/lib/gitlab/memory/watchdog/configurator_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/configurator_spec.rb @@ -2,6 +2,7 @@ require 'fast_spec_helper' require 'prometheus/client' +require 'sidekiq' require_dependency 'gitlab/cluster/lifecycle_events' RSpec.describe Gitlab::Memory::Watchdog::Configurator do @@ -184,4 +185,15 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do end end # rubocop: enable RSpec/VerifiedDoubles + + describe '.configure_for_sidekiq' do + let(:logger) { ::Sidekiq.logger } + + subject(:configurator) { described_class.configure_for_sidekiq } + + it_behaves_like 'as configurator', + Gitlab::Memory::Watchdog::TermProcessHandler, + 'SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', + 3 + end end diff --git a/spec/models/factories_spec.rb b/spec/models/factories_spec.rb index 072f5496bca..65b993cca7f 100644 --- a/spec/models/factories_spec.rb +++ b/spec/models/factories_spec.rb @@ -22,6 +22,7 @@ RSpec.describe 'factories', :saas do [:ci_job_artifact, :raw], [:ci_job_artifact, :gzip], [:ci_job_artifact, :correct_checksum], + [:dependency_proxy_blob, :remote_store], [:environment, :non_playable], [:composer_cache_file, :object_storage], [:debian_project_component_file, :object_storage], @@ -50,6 +51,7 @@ RSpec.describe 'factories', :saas do [:ee_ci_job_artifact, :v2], [:ee_ci_job_artifact, :v2_1], [:geo_ci_secure_file_state, any], + [:geo_dependency_proxy_blob_state, any], [:geo_event_log, :geo_event], [:geo_job_artifact_state, any], [:geo_lfs_object_state, any], diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 5edea13afa4..26def474b88 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -399,159 +399,7 @@ RSpec.describe SearchService do end end - context 'redacting search results' do - let(:search) { 'anything' } - - subject(:result) { search_service.search_objects } - - shared_examples "redaction limits N+1 queries" do |limit:| - it 'does not exceed the query limit' do - # issuing the query to remove the data loading call - unredacted_results.to_a - - # only the calls from the redaction are left - query = ActiveRecord::QueryRecorder.new { result } - - # these are the project authorization calls, which are not preloaded - expect(query.count).to be <= limit - end - end - - def found_blob(project) - Gitlab::Search::FoundBlob.new(project: project) - end - - def found_wiki_page(project) - Gitlab::Search::FoundWikiPage.new(found_blob(project)) - end - - before do - expect(search_service) - .to receive(:search_results) - .and_return(double('search results', objects: unredacted_results)) - end - - def ar_relation(klass, *objects) - klass.id_in(objects.map(&:id)) - end - - def kaminari_array(*objects) - Kaminari.paginate_array(objects).page(1).per(20) - end - - context 'issues' do - let(:readable) { create(:issue, project: accessible_project) } - let(:unreadable) { create(:issue, project: inaccessible_project) } - let(:unredacted_results) { ar_relation(Issue, readable, unreadable) } - let(:scope) { 'issues' } - - it 'redacts the inaccessible issue' do - expect(result).to contain_exactly(readable) - end - end - - context 'notes' do - let(:readable) { create(:note_on_commit, project: accessible_project) } - let(:unreadable) { create(:note_on_commit, project: inaccessible_project) } - let(:unredacted_results) { ar_relation(Note, readable, unreadable) } - let(:scope) { 'notes' } - - it 'redacts the inaccessible note' do - expect(result).to contain_exactly(readable) - end - end - - context 'merge_requests' do - let(:readable) { create(:merge_request, source_project: accessible_project, author: user) } - let(:unreadable) { create(:merge_request, source_project: inaccessible_project) } - let(:unredacted_results) { ar_relation(MergeRequest, readable, unreadable) } - let(:scope) { 'merge_requests' } - - it 'redacts the inaccessible merge request' do - expect(result).to contain_exactly(readable) - end - - context 'with :with_api_entity_associations' do - let(:unredacted_results) { ar_relation(MergeRequest.with_api_entity_associations, readable, unreadable) } - - it_behaves_like "redaction limits N+1 queries", limit: 8 - end - end - - context 'project repository blobs' do - let(:readable) { found_blob(accessible_project) } - let(:unreadable) { found_blob(inaccessible_project) } - let(:unredacted_results) { kaminari_array(readable, unreadable) } - let(:scope) { 'blobs' } - - it 'redacts the inaccessible blob' do - expect(result).to contain_exactly(readable) - end - end - - context 'project wiki blobs' do - let(:readable) { found_wiki_page(accessible_project) } - let(:unreadable) { found_wiki_page(inaccessible_project) } - let(:unredacted_results) { kaminari_array(readable, unreadable) } - let(:scope) { 'wiki_blobs' } - - it 'redacts the inaccessible blob' do - expect(result).to contain_exactly(readable) - end - end - - context 'project snippets' do - let(:readable) { create(:project_snippet, project: accessible_project) } - let(:unreadable) { create(:project_snippet, project: inaccessible_project) } - let(:unredacted_results) { ar_relation(ProjectSnippet, readable, unreadable) } - let(:scope) { 'snippet_titles' } - - it 'redacts the inaccessible snippet' do - expect(result).to contain_exactly(readable) - end - - context 'with :with_api_entity_associations' do - it_behaves_like "redaction limits N+1 queries", limit: 14 - end - end - - context 'personal snippets' do - let(:readable) { create(:personal_snippet, :private, author: user) } - let(:unreadable) { create(:personal_snippet, :private) } - let(:unredacted_results) { ar_relation(PersonalSnippet, readable, unreadable) } - let(:scope) { 'snippet_titles' } - - it 'redacts the inaccessible snippet' do - expect(result).to contain_exactly(readable) - end - - context 'with :with_api_entity_associations' do - it_behaves_like "redaction limits N+1 queries", limit: 4 - end - end - - context 'commits' do - let(:readable) { accessible_project.commit } - let(:unreadable) { inaccessible_project.commit } - let(:unredacted_results) { kaminari_array(readable, unreadable) } - let(:scope) { 'commits' } - - it 'redacts the inaccessible commit' do - expect(result).to contain_exactly(readable) - end - end - - context 'users' do - let(:other_user) { create(:user) } - let(:unredacted_results) { ar_relation(User, user, other_user) } - let(:scope) { 'users' } - - it 'passes the users through' do - # Users are always visible to everyone - expect(result).to contain_exactly(user, other_user) - end - end - end + it_behaves_like 'a redacted search results' end describe '#valid_request?' do diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index f41457d2420..24c768258a1 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -38,6 +38,10 @@ module StubConfiguration allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options) end + def stub_dependency_proxy_setting(messages) + allow(Gitlab.config.dependency_proxy).to receive_messages(to_settings(messages)) + end + def stub_gravatar_setting(messages) allow(Gitlab.config.gravatar).to receive_messages(to_settings(messages)) end diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb new file mode 100644 index 00000000000..4d242d0e719 --- /dev/null +++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb @@ -0,0 +1,304 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a redacted search results' do + let_it_be(:user) { create(:user) } + + let_it_be(:accessible_group) { create(:group, :private) } + let_it_be(:accessible_project) { create(:project, :repository, :private, name: 'accessible_project') } + + let_it_be(:group_member) { create(:group_member, group: accessible_group, user: user) } + + let_it_be(:inaccessible_group) { create(:group, :private) } + let_it_be(:inaccessible_project) { create(:project, :repository, :private, name: 'inaccessible_project') } + + let(:search) { 'anything' } + + subject(:result) { search_service.search_objects } + + def found_blob(project) + Gitlab::Search::FoundBlob.new(project: project) + end + + def found_wiki_page(project) + Gitlab::Search::FoundWikiPage.new(found_blob(project)) + end + + def ar_relation(klass, *objects) + klass.id_in(objects.map(&:id)) + end + + def kaminari_array(*objects) + Kaminari.paginate_array(objects).page(1).per(20) + end + + before do + accessible_project.add_maintainer(user) + + allow(search_service) + .to receive_message_chain(:search_results, :objects) + .and_return(unredacted_results) + end + + context 'for issues' do + let(:readable) { create(:issue, project: accessible_project) } + let(:unreadable) { create(:issue, project: inaccessible_project) } + let(:unredacted_results) { ar_relation(Issue, readable, unreadable) } + let(:scope) { 'issues' } + + it 'redacts the inaccessible issue' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Issue', id: unreadable.id, ability: :read_issue } + ]))) + + expect(result).to contain_exactly(readable) + end + end + + context 'for notes' do + let(:readable_merge_request) do + create(:merge_request_with_diffs, target_project: accessible_project, source_project: accessible_project) + end + + let(:readable_note_on_commit) { create(:note_on_commit, project: accessible_project) } + let(:readable_diff_note) { create(:diff_note_on_commit, project: accessible_project) } + let(:readable_note_on_mr) do + create(:discussion_note_on_merge_request, noteable: readable_merge_request, project: accessible_project) + end + + let(:readable_diff_note_on_mr) do + create(:diff_note_on_merge_request, noteable: readable_merge_request, project: accessible_project) + end + + let(:readable_note_on_project_snippet) do + create(:note_on_project_snippet, noteable: readable_merge_request, project: accessible_project) + end + + let(:unreadable_merge_request) do + create(:merge_request_with_diffs, target_project: inaccessible_project, source_project: inaccessible_project) + end + + let(:unreadable_note_on_commit) { create(:note_on_commit, project: inaccessible_project) } + let(:unreadable_diff_note) { create(:diff_note_on_commit, project: inaccessible_project) } + let(:unreadable_note_on_mr) do + create(:discussion_note_on_merge_request, noteable: unreadable_merge_request, project: inaccessible_project) + end + + let(:unreadable_note_on_project_snippet) do + create(:note_on_project_snippet, noteable: unreadable_merge_request, project: inaccessible_project) + end + + let(:unredacted_results) do + ar_relation(Note, + readable_note_on_commit, + readable_diff_note, + readable_note_on_mr, + readable_diff_note_on_mr, + readable_note_on_project_snippet, + unreadable_note_on_commit, + unreadable_diff_note, + unreadable_note_on_mr, + unreadable_note_on_project_snippet) + end + + let(:scope) { 'notes' } + + it 'redacts the inaccessible notes' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note }, + { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note }, + { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note }, + { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note } + ]))) + + expect(result).to contain_exactly(readable_note_on_commit, + readable_diff_note, + readable_note_on_mr, + readable_diff_note_on_mr, + readable_note_on_project_snippet) + end + end + + context 'for merge_requests' do + let(:readable) { create(:merge_request, source_project: accessible_project) } + let(:unreadable) { create(:merge_request, source_project: inaccessible_project) } + let(:unredacted_results) { ar_relation(MergeRequest, readable, unreadable) } + let(:scope) { 'merge_requests' } + + it 'redacts the inaccessible merge request' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request } + ]))) + + expect(result).to contain_exactly(readable) + end + + context 'with :with_api_entity_associations' do + let(:unredacted_results) { ar_relation(MergeRequest.with_api_entity_associations, readable, unreadable) } + + it_behaves_like "redaction limits N+1 queries", limit: 8 + end + end + + context 'for blobs' do + let(:readable) { found_blob(accessible_project) } + let(:unreadable) { found_blob(inaccessible_project) } + let(:unredacted_results) { kaminari_array(readable, unreadable) } + let(:scope) { 'blobs' } + + it 'redacts the inaccessible blob' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob } + ]))) + + expect(result).to contain_exactly(readable) + end + end + + context 'for wiki blobs' do + let(:readable) { found_wiki_page(accessible_project) } + let(:unreadable) { found_wiki_page(inaccessible_project) } + let(:unredacted_results) { kaminari_array(readable, unreadable) } + let(:scope) { 'wiki_blobs' } + + it 'redacts the inaccessible blob' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page } + ]))) + + expect(result).to contain_exactly(readable) + end + end + + context 'for project snippets' do + let(:readable) { create(:project_snippet, project: accessible_project) } + let(:unreadable) { create(:project_snippet, project: inaccessible_project) } + let(:unredacted_results) { ar_relation(ProjectSnippet, readable, unreadable) } + let(:scope) { 'snippet_titles' } + + it 'redacts the inaccessible snippet' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet } + ]))) + + expect(result).to contain_exactly(readable) + end + + context 'with :with_api_entity_associations' do + it_behaves_like "redaction limits N+1 queries", limit: 14 + end + end + + context 'for personal snippets' do + let(:readable) { create(:personal_snippet, :private, author: user) } + let(:unreadable) { create(:personal_snippet, :private) } + let(:unredacted_results) { ar_relation(PersonalSnippet, readable, unreadable) } + let(:scope) { 'snippet_titles' } + + it 'redacts the inaccessible snippet' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet } + ]))) + + expect(result).to contain_exactly(readable) + end + + context 'with :with_api_entity_associations' do + it_behaves_like "redaction limits N+1 queries", limit: 4 + end + end + + context 'for commits' do + let(:readable) { accessible_project.commit } + let(:unreadable) { inaccessible_project.commit } + let(:unredacted_results) { kaminari_array(readable, unreadable) } + let(:scope) { 'commits' } + + it 'redacts the inaccessible commit' do + expect(search_service.send(:logger)) + .to receive(:error) + .with(hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Commit', id: unreadable.id, ability: :read_commit } + ]))) + + expect(result).to contain_exactly(readable) + end + end + + context 'for users' do + let(:other_user) { create(:user) } + let(:unredacted_results) { ar_relation(User, user, other_user) } + let(:scope) { 'users' } + + it 'passes the users through' do + # Users are always visible to everyone + expect(result).to contain_exactly(user, other_user) + end + end +end + +RSpec.shared_examples "redaction limits N+1 queries" do |limit:| + it 'does not exceed the query limit' do + # issuing the query to remove the data loading call + unredacted_results.to_a + + # only the calls from the redaction are left + query = ActiveRecord::QueryRecorder.new { result } + + # these are the project authorization calls, which are not preloaded + expect(query.count).to be <= limit + end +end diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb index 565dadd64fe..5f9c6c65a08 100644 --- a/spec/views/search/show.html.haml_spec.rb +++ b/spec/views/search/show.html.haml_spec.rb @@ -11,10 +11,10 @@ RSpec.describe 'search/show' do stub_template "search/_results.html.haml" => 'Results Partial' end - context 'feature flag enabled' do + context 'search_page_vertical_nav feature flag enabled' do before do - allow(self).to receive(:current_user).and_return(user) - @search_term = search_term + allow(view).to receive(:current_user) { user } + assign(:search_term, search_term) render end @@ -29,11 +29,11 @@ RSpec.describe 'search/show' do end end - context 'feature flag disabled' do + context 'search_page_vertical_nav feature flag disabled' do before do stub_feature_flags(search_page_vertical_nav: false) - @search_term = search_term + assign(:search_term, search_term) render end diff --git a/yarn.lock b/yarn.lock index 27a3f834065..3e1e1b88281 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4992,10 +4992,10 @@ dompurify@2.3.8: resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f" integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw== -dompurify@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.0.tgz#c9c88390f024c2823332615c9e20a453cf3825dd" - integrity sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA== +dompurify@^2.4.0, dompurify@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631" + integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA== domutils@^2.5.2, domutils@^2.6.0: version "2.6.0" |