diff options
72 files changed, 758 insertions, 683 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index 02fc58f8580..69a0427a31c 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -42,7 +42,7 @@ review-docs-cleanup: docs-lint links: extends: - .docs:rules:docs-lint - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-html:alpine-3.16-ruby-2.7.6-0bc327a4 + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-html:alpine-3.16-ruby-2.7.7-8ae1f7da stage: lint needs: [] script: @@ -58,7 +58,7 @@ docs-lint links: .docs-markdown-lint-image: # When updating the image version here, update it in /scripts/lint-doc.sh too. - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2 + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.16-vale-2.22.0-markdownlint-0.32.2-markdownlint2-0.6.0 docs-lint markdown: extends: diff --git a/.ruby-version b/.ruby-version index 1f7da99d4e1..eca690e737b 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.7 +3.0.5 diff --git a/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue b/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue index c716afbbcf0..85a43aa41e5 100644 --- a/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue +++ b/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue @@ -18,14 +18,17 @@ export default { reportAbusePath: { default: '', }, + }, + props: { reportedUserId: { - default: '', + type: Number, + required: true, }, reportedFromUrl: { + type: String, + required: false, default: '', }, - }, - props: { showDrawer: { type: Boolean, required: true, @@ -39,8 +42,8 @@ export default { }, categoryOptions: [ { value: 'spam', text: s__("ReportAbuse|They're posting spam.") }, - { value: 'offensive', text: s__("ReportAbuse|They're being offsensive or abusive.") }, - { value: 'phishing', text: s__("ReportAbuse|They're phising.") }, + { value: 'offensive', text: s__("ReportAbuse|They're being offensive or abusive.") }, + { value: 'phishing', text: s__("ReportAbuse|They're phishing.") }, { value: 'crypto', text: s__("ReportAbuse|They're crypto mining.") }, { value: 'credentials', @@ -94,7 +97,7 @@ export default { data-testid="input-referer" /> - <gl-form-group :label="$options.i18n.label"> + <gl-form-group :label="$options.i18n.label" label-class="gl-text-black-normal"> <gl-form-radio-group v-model="selected" :options="$options.categoryOptions" diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue index 53e574e9942..43a2b13b81c 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue @@ -1,5 +1,5 @@ <script> -import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui'; import { mapGetters, mapActions } from 'vuex'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; import { joinPaths } from '~/lib/utils/url_utility'; @@ -13,6 +13,7 @@ export default { GlButton, GlFormGroup, GlFormInput, + GlLink, BoardEditableItem, }, directives: { @@ -130,7 +131,11 @@ export default { @off-click="handleOffClick" > <template #title> - <span data-testid="item-title">{{ item.title }}</span> + <span data-testid="item-title"> + <gl-link class="gl-reset-color gl-hover-text-blue-800" :href="item.webUrl"> + {{ item.title }} + </gl-link> + </span> </template> <template #collapsed> <span class="gl-text-gray-800">{{ item.referencePath }}</span> diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 56e360c75e3..66f9a253e24 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -98,6 +98,12 @@ export default { submitAsSpamPath: { default: '', }, + reportedUserId: { + default: '', + }, + reportedFromUrl: { + default: '', + }, }, data() { return { @@ -369,6 +375,8 @@ export default { /> <abuse-category-selector + :reported-user-id="reportedUserId" + :reported-from-url="reportedFromUrl" :show-drawer="isReportAbuseDrawerOpen" @close-drawer="toggleReportAbuseDrawer(false)" /> diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index 21d877c5fe6..8b8c710c641 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -152,7 +152,7 @@ export function initHeaderActions(store, type = '') { projectPath: el.dataset.projectPath, projectId: el.dataset.projectId, reportAbusePath: el.dataset.reportAbusePath, - reportedUserId: el.dataset.reportedUserId, + reportedUserId: parseInt(el.dataset.reportedUserId, 10), reportedFromUrl: el.dataset.reportedFromUrl, submitAsSpamPath: el.dataset.submitAsSpamPath, }, diff --git a/app/assets/javascripts/mr_notes/init_notes.js b/app/assets/javascripts/mr_notes/init_notes.js index f5f10aa4a9b..d968c125068 100644 --- a/app/assets/javascripts/mr_notes/init_notes.js +++ b/app/assets/javascripts/mr_notes/init_notes.js @@ -23,6 +23,7 @@ export default () => { } const notesFilterProps = getNotesFilterData(el); + const notesDataset = el.dataset; // eslint-disable-next-line no-new new Vue({ @@ -32,8 +33,10 @@ export default () => { NotesApp, }, store, + provide: { + reportAbusePath: notesDataset.reportAbusePath, + }, data() { - const notesDataset = el.dataset; const noteableData = JSON.parse(notesDataset.noteableData); noteableData.noteableType = notesDataset.noteableType; noteableData.targetType = notesDataset.targetType; diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index c15c11ed9db..b9d5fe5c900 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -10,6 +10,7 @@ import eventHub from '~/sidebar/event_hub'; import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { splitCamelCase } from '~/lib/utils/text_utility'; +import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import ReplyButton from './note_actions/reply_button.vue'; import TimelineEventButton from './note_actions/timeline_event_button.vue'; @@ -30,6 +31,7 @@ export default { GlDropdownItem, UserAccessRoleBadge, EmojiPicker: () => import('~/emoji/components/picker.vue'), + AbuseCategorySelector, }, directives: { GlTooltip: GlTooltipDirective, @@ -58,11 +60,6 @@ export default { required: false, default: '', }, - reportAbusePath: { - type: String, - required: false, - default: null, - }, isAuthor: { type: Boolean, required: false, @@ -135,6 +132,11 @@ export default { default: '', }, }, + data() { + return { + isReportAbuseDrawerOpen: false, + }; + }, computed: { ...mapState(['isPromoteCommentToTimelineEventInProgress']), ...mapGetters(['getUserDataByProp', 'getNoteableData', 'canUserAddIncidentTimelineEvents']), @@ -252,6 +254,9 @@ export default { awardName, }); }, + toggleReportAbuseDrawer(isOpen) { + this.isReportAbuseDrawerOpen = isOpen; + }, }, }; </script> @@ -362,7 +367,11 @@ export default { /> <!-- eslint-enable @gitlab/vue-no-data-toggle --> <ul class="dropdown-menu more-actions-dropdown dropdown-open-left"> - <gl-dropdown-item v-if="canReportAsAbuse" :href="reportAbusePath"> + <gl-dropdown-item + v-if="canReportAsAbuse" + data-testid="report-abuse-button" + @click="toggleReportAbuseDrawer(true)" + > {{ $options.i18n.reportAbuse }} </gl-dropdown-item> <gl-dropdown-item @@ -380,5 +389,12 @@ export default { </gl-dropdown-item> </ul> </div> + <abuse-category-selector + v-if="canReportAsAbuse" + :reported-user-id="authorId" + :reported-from-url="noteUrl" + :show-drawer="isReportAbuseDrawerOpen" + @close-drawer="toggleReportAbuseDrawer(false)" + /> </div> </template> diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 826e7e5a3d0..93575ad57ff 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -43,6 +43,11 @@ export default { SafeHtml, }, mixins: [noteable, resolvable], + inject: { + reportAbusePath: { + default: '', + }, + }, props: { note: { type: Object, @@ -129,7 +134,7 @@ export default { }; }, canReportAsAbuse() { - return Boolean(this.note.report_abuse_path) && this.author.id !== this.getUserData.id; + return Boolean(this.reportAbusePath) && this.author.id !== this.getUserData.id; }, noteAnchorId() { return `note_${this.note.id}`; @@ -488,7 +493,6 @@ export default { :can-delete="note.current_user.can_edit" :can-report-as-abuse="canReportAsAbuse" :can-resolve="canResolve" - :report-abuse-path="note.report_abuse_path" :resolvable="note.resolvable || note.isDraft" :is-resolved="note.resolved || note.resolve_discussion" :is-resolving="isResolving" diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 95263e666b2..2e09c9f2288 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -52,6 +52,7 @@ export default () => { store, provide: { showTimelineViewToggle, + reportAbusePath: notesDataset.reportAbusePath, }, data() { return { diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue deleted file mode 100644 index e1cf4883029..00000000000 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_package.vue +++ /dev/null @@ -1,62 +0,0 @@ -<script> -import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql'; -import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash'; -import { s__ } from '~/locale'; - -import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/package_registry/constants'; - -export default { - props: { - refetchQueries: { - type: Array, - required: false, - default: null, - }, - showSuccessAlert: { - type: Boolean, - required: false, - default: false, - }, - }, - i18n: { - errorMessage: s__('PackageRegistry|Something went wrong while deleting the package.'), - successMessage: DELETE_PACKAGE_SUCCESS_MESSAGE, - }, - methods: { - async deletePackage(packageEntity) { - try { - this.$emit('start'); - const { data } = await this.$apollo.mutate({ - mutation: destroyPackageMutation, - variables: { - id: packageEntity.id, - }, - awaitRefetchQueries: Boolean(this.refetchQueries), - refetchQueries: this.refetchQueries, - }); - - if (data?.destroyPackage?.errors[0]) { - throw data.destroyPackage.errors[0]; - } - if (this.showSuccessAlert) { - createAlert({ - message: this.$options.i18n.successMessage, - variant: VARIANT_SUCCESS, - }); - } - } catch (error) { - createAlert({ - message: this.$options.i18n.errorMessage, - variant: VARIANT_WARNING, - captureError: true, - error, - }); - } - this.$emit('end'); - }, - }, - render() { - return this.$scopedSlots.default({ deletePackage: this.deletePackage }); - }, -}; -</script> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue new file mode 100644 index 00000000000..0914c013108 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue @@ -0,0 +1,76 @@ +<script> +import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; +import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash'; + +import { + DELETE_PACKAGE_ERROR_MESSAGE, + DELETE_PACKAGE_SUCCESS_MESSAGE, + DELETE_PACKAGES_ERROR_MESSAGE, + DELETE_PACKAGES_SUCCESS_MESSAGE, +} from '~/packages_and_registries/package_registry/constants'; + +export default { + name: 'DeletePackages', + props: { + refetchQueries: { + type: Array, + required: false, + default: null, + }, + showSuccessAlert: { + type: Boolean, + required: false, + default: false, + }, + }, + i18n: { + errorMessage: DELETE_PACKAGE_ERROR_MESSAGE, + errorMessageMultiple: DELETE_PACKAGES_ERROR_MESSAGE, + successMessage: DELETE_PACKAGE_SUCCESS_MESSAGE, + successMessageMultiple: DELETE_PACKAGES_SUCCESS_MESSAGE, + }, + methods: { + async deletePackages(packageEntities) { + const isSinglePackage = packageEntities.length === 1; + try { + this.$emit('start'); + const ids = packageEntities.map((packageEntity) => packageEntity.id); + const { data } = await this.$apollo.mutate({ + mutation: destroyPackagesMutation, + variables: { + ids, + }, + awaitRefetchQueries: Boolean(this.refetchQueries), + refetchQueries: this.refetchQueries, + }); + + if (data?.destroyPackages?.errors[0]) { + throw data.destroyPackages.errors[0]; + } + + if (this.showSuccessAlert) { + createAlert({ + message: isSinglePackage + ? this.$options.i18n.successMessage + : this.$options.i18n.successMessageMultiple, + variant: VARIANT_SUCCESS, + }); + } + } catch (error) { + createAlert({ + message: isSinglePackage + ? this.$options.i18n.errorMessage + : this.$options.i18n.errorMessageMultiple, + variant: VARIANT_WARNING, + captureError: true, + error, + }); + } + this.$emit('end'); + }, + }, + render() { + return this.$scopedSlots.default({ deletePackages: this.deletePackages }); + }, +}; +</script> 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 40bf7b7e143..486ab4fdc99 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 @@ -112,7 +112,7 @@ export default { this.itemsToBeDeleted = []; }, deleteItemConfirmation() { - this.$emit('package:delete', this.itemToBeDeleted); + this.$emit('delete', [this.itemToBeDeleted]); this.track(DELETE_PACKAGE_TRACKING_ACTION); this.itemToBeDeleted = null; }, 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 539b12bd6db..beb8d11a944 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -124,6 +124,10 @@ 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 DELETE_PACKAGE_ERROR_MESSAGE = s__( + 'PackageRegistry|Something went wrong while deleting the package.', +); + export const PACKAGE_REGISTRY_TITLE = __('Package Registry'); export const PACKAGE_ERROR_STATUS = 'ERROR'; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql deleted file mode 100644 index 884980f24a9..00000000000 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql +++ /dev/null @@ -1,5 +0,0 @@ -mutation destroyPackage($id: PackagesPackageID!) { - destroyPackage(input: { id: $id }) { - errors - } -} diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue index 03352f01aca..607f5c2af86 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue @@ -23,7 +23,7 @@ import PackageFiles from '~/packages_and_registries/package_registry/components/ import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; -import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import { PACKAGE_TYPE_NUGET, PACKAGE_TYPE_COMPOSER, @@ -71,7 +71,7 @@ export default { AdditionalMetadata, InstallationCommands, PackageFiles, - DeletePackage, + DeletePackages, PackageVersionsList, }, directives: { @@ -418,11 +418,11 @@ export default { </gl-tab> </gl-tabs> - <delete-package + <delete-packages @start="track($options.trackingActions.DELETE_PACKAGE_TRACKING_ACTION)" @end="navigateToListWithSuccessModal" > - <template #default="{ deletePackage }"> + <template #default="{ deletePackages }"> <gl-modal ref="deleteModal" size="sm" @@ -430,7 +430,7 @@ export default { data-testid="delete-modal" :action-primary="$options.modal.packageDeletePrimaryAction" :action-cancel="$options.modal.cancelAction" - @primary="deletePackage(packageEntity)" + @primary="deletePackages([packageEntity])" @hidden="resetDeleteModalContent" @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)" > @@ -446,7 +446,7 @@ export default { </gl-sprintf> </gl-modal> </template> - </delete-package> + </delete-packages> <gl-modal ref="deleteFileModal" 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 396429d60d8..31c76c95e45 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,6 +1,6 @@ <script> -import { GlAlert, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; -import { createAlert, VARIANT_INFO, VARIANT_SUCCESS, VARIANT_DANGER } from '~/flash'; +import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; +import { createAlert, VARIANT_INFO } from '~/flash'; import { historyReplaceState } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages_and_registries/shared/constants'; @@ -9,33 +9,28 @@ import { GROUP_RESOURCE_TYPE, GRAPHQL_PAGE_SIZE, DELETE_PACKAGE_SUCCESS_MESSAGE, - DELETE_PACKAGES_ERROR_MESSAGE, - DELETE_PACKAGES_SUCCESS_MESSAGE, EMPTY_LIST_HELP_URL, PACKAGE_HELP_URL, } 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 DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.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'; export default { components: { - GlAlert, GlEmptyState, GlLink, GlSprintf, PackageList, PackageTitle, PackageSearch, - DeletePackage, + DeletePackages, }, inject: ['emptyListIllustration', 'isGroupPage', 'fullPath'], data() { return { - alertVariables: null, packages: {}, sort: '', filters: {}, @@ -114,39 +109,6 @@ export default { historyReplaceState(cleanUrl); } }, - async deletePackages(packageEntities) { - this.mutationLoading = true; - try { - const { data } = await this.$apollo.mutate({ - mutation: destroyPackagesMutation, - variables: { - ids: packageEntities.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: VARIANT_SUCCESS, - message: DELETE_PACKAGES_SUCCESS_MESSAGE, - }); - } catch { - this.showAlert({ - variant: VARIANT_DANGER, - message: DELETE_PACKAGES_ERROR_MESSAGE, - }); - } finally { - this.mutationLoading = false; - } - }, handleSearchUpdate({ sort, filters }) { this.sort = sort; this.filters = { ...filters }; @@ -180,9 +142,6 @@ export default { updateQuery: this.updateQuery, }); }, - showAlert(obj) { - this.alertVariables = { ...obj }; - }, }, i18n: { widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'), @@ -201,32 +160,22 @@ export default { <template> <div> - <gl-alert - v-if="alertVariables" - :variant="alertVariables.variant" - class="gl-mt-5" - dismissible - @dismiss="alertVariables = null" - > - {{ alertVariables.message }} - </gl-alert> <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount" /> <package-search class="gl-mb-5" @update="handleSearchUpdate" /> - <delete-package + <delete-packages :refetch-queries="refetchQueriesData" show-success-alert @start="mutationLoading = true" @end="mutationLoading = false" > - <template #default="{ deletePackage }"> + <template #default="{ deletePackages }"> <package-list :list="packages.nodes" :is-loading="isLoading" :page-info="pageInfo" @prev-page="fetchPreviousPage" @next-page="fetchNextPage" - @package:delete="deletePackage" @delete="deletePackages" > <template #empty-state> @@ -245,6 +194,6 @@ export default { </template> </package-list> </template> - </delete-package> + </delete-packages> </div> </template> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 5fa3288bbef..f2bc4796324 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -49,14 +49,10 @@ export default { infrastructureLabel: s__('ProjectSettings|Infrastructure'), infrastructureHelpText: s__('ProjectSettings|Configure your infrastructure.'), monitorLabel: s__('ProjectSettings|Monitor'), - packagesHelpText: s__( - 'ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public.', - ), packageRegistryHelpText: s__('ProjectSettings|Publish, store, and view packages in a project.'), packageRegistryForEveryoneHelpText: s__( 'ProjectSettings|Anyone can pull packages with a package manager API.', ), - packagesLabel: s__('ProjectSettings|Packages'), packageRegistryLabel: s__('ProjectSettings|Package registry'), packageRegistryForEveryoneLabel: s__( 'ProjectSettings|Allow anyone to pull from Package Registry', @@ -355,9 +351,6 @@ export default { this.visibilityLevel < this.currentSettings.visibilityLevel ); }, - packageRegistryAccessLevelEnabled() { - return this.glFeatures.packageRegistryAccessLevel; - }, packageRegistryEnabled() { return this.packageRegistryAccessLevel > featureAccessLevel.NOT_ENABLED; }, @@ -392,14 +385,12 @@ export default { featureAccessLevel.PROJECT_MEMBERS, this.buildsAccessLevel, ); - if (this.packageRegistryAccessLevelEnabled) { - if ( - this.packageRegistryAccessLevel === featureAccessLevel.EVERYONE || - (this.packageRegistryAccessLevel > featureAccessLevel.EVERYONE && - oldValue === VISIBILITY_LEVEL_PUBLIC_INTEGER) - ) { - this.packageRegistryAccessLevel = featureAccessLevel.PROJECT_MEMBERS; - } + if ( + this.packageRegistryAccessLevel === featureAccessLevel.EVERYONE || + (this.packageRegistryAccessLevel > featureAccessLevel.EVERYONE && + oldValue === VISIBILITY_LEVEL_PUBLIC_INTEGER) + ) { + this.packageRegistryAccessLevel = featureAccessLevel.PROJECT_MEMBERS; } this.wikiAccessLevel = Math.min(featureAccessLevel.PROJECT_MEMBERS, this.wikiAccessLevel); this.snippetsAccessLevel = Math.min( @@ -459,10 +450,7 @@ export default { this.repositoryAccessLevel = featureAccessLevel.EVERYONE; if (this.mergeRequestsAccessLevel > featureAccessLevel.NOT_ENABLED) this.mergeRequestsAccessLevel = featureAccessLevel.EVERYONE; - if ( - this.packageRegistryAccessLevelEnabled && - this.packageRegistryAccessLevel === featureAccessLevel.PROJECT_MEMBERS - ) { + if (this.packageRegistryAccessLevel === featureAccessLevel.PROJECT_MEMBERS) { this.packageRegistryAccessLevel = PACKAGE_REGISTRY_ACCESS_LEVEL_DEFAULT_BY_PROJECT_VISIBILITY[value]; } @@ -488,19 +476,17 @@ export default { this.containerRegistryAccessLevel = featureAccessLevel.EVERYONE; this.highlightChanges(); - } else if (this.packageRegistryAccessLevelEnabled) { - if ( - value === VISIBILITY_LEVEL_PUBLIC_INTEGER && - this.packageRegistryAccessLevel === featureAccessLevel.EVERYONE - ) { - // eslint-disable-next-line prefer-destructuring - this.packageRegistryAccessLevel = FEATURE_ACCESS_LEVEL_ANONYMOUS[0]; - } else if ( - value === VISIBILITY_LEVEL_INTERNAL_INTEGER && - this.packageRegistryAccessLevel === FEATURE_ACCESS_LEVEL_ANONYMOUS[0] - ) { - this.packageRegistryAccessLevel = featureAccessLevel.EVERYONE; - } + } else if ( + value === VISIBILITY_LEVEL_PUBLIC_INTEGER && + this.packageRegistryAccessLevel === featureAccessLevel.EVERYONE + ) { + // eslint-disable-next-line prefer-destructuring + this.packageRegistryAccessLevel = FEATURE_ACCESS_LEVEL_ANONYMOUS[0]; + } else if ( + value === VISIBILITY_LEVEL_INTERNAL_INTEGER && + this.packageRegistryAccessLevel === FEATURE_ACCESS_LEVEL_ANONYMOUS[0] + ) { + this.packageRegistryAccessLevel = featureAccessLevel.EVERYONE; } }, @@ -770,22 +756,6 @@ export default { </p> </project-setting-row> <project-setting-row - v-if="packagesAvailable && !packageRegistryAccessLevelEnabled" - ref="package-settings" - :help-path="packagesHelpPath" - :label="$options.i18n.packagesLabel" - :help-text="$options.i18n.packagesHelpText" - > - <gl-toggle - v-model="packagesEnabled" - class="gl-my-2" - :disabled="!repositoryEnabled" - :label="$options.i18n.packagesLabel" - label-position="hidden" - name="project[packages_enabled]" - /> - </project-setting-row> - <project-setting-row ref="pipeline-settings" :label="$options.i18n.ciCdLabel" :help-text="s__('ProjectSettings|Build, test, and deploy your changes.')" @@ -889,7 +859,7 @@ export default { /> </project-setting-row> <project-setting-row - v-if="packageRegistryAccessLevelEnabled && packagesAvailable" + v-if="packagesAvailable" :help-path="packagesHelpPath" :label="$options.i18n.packageRegistryLabel" :help-text="$options.i18n.packageRegistryHelpText" diff --git a/app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue b/app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue index 31890249f41..ff76ca7c862 100644 --- a/app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue +++ b/app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue @@ -12,6 +12,7 @@ export default { MountingPortal, AbuseCategorySelector, }, + inject: ['reportedUserId', 'reportedFromUrl'], i18n: { reportAbuse: s__('ReportAbuse|Report abuse to administrator'), }, @@ -21,21 +22,23 @@ export default { }; }, methods: { - openDrawer() { - this.open = true; - }, - closeDrawer() { - this.open = false; + toggleDrawer(open) { + this.open = open; }, }, }; </script> <template> <span> - <gl-dropdown-item @click="openDrawer">{{ $options.i18n.reportAbuse }}</gl-dropdown-item> + <gl-dropdown-item @click="toggleDrawer(true)">{{ $options.i18n.reportAbuse }}</gl-dropdown-item> <mounting-portal mount-to="#js-report-abuse-drawer" name="abuse-category-selector" append> - <abuse-category-selector :show-drawer="open" @close-drawer="closeDrawer" /> + <abuse-category-selector + :reported-user-id="reportedUserId" + :reported-from-url="reportedFromUrl" + :show-drawer="open" + @close-drawer="toggleDrawer(false)" + /> </mounting-portal> </span> </template> diff --git a/app/assets/javascripts/projects/merge_requests/index.js b/app/assets/javascripts/projects/merge_requests/index.js index 25a70121d68..fd85a8d0dba 100644 --- a/app/assets/javascripts/projects/merge_requests/index.js +++ b/app/assets/javascripts/projects/merge_requests/index.js @@ -10,7 +10,12 @@ export const initReportAbuse = () => { return new Vue({ el, - provide: { reportAbusePath, reportedUserId, reportedFromUrl }, + name: 'ReportAbuseDropdownItemRoot', + provide: { + reportAbusePath, + reportedUserId: parseInt(reportedUserId, 10), + reportedFromUrl, + }, render(createElement) { return createElement(ReportAbuseDropdownItem); }, diff --git a/app/assets/javascripts/users/profile/components/report_abuse_button.vue b/app/assets/javascripts/users/profile/components/report_abuse_button.vue index aabb7fde396..0e41a214888 100644 --- a/app/assets/javascripts/users/profile/components/report_abuse_button.vue +++ b/app/assets/javascripts/users/profile/components/report_abuse_button.vue @@ -1,6 +1,6 @@ <script> import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; @@ -14,8 +14,9 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + inject: ['reportedUserId', 'reportedFromUrl'], i18n: { - reportAbuse: __('Report abuse to administrator'), + reportAbuse: s__('ReportAbuse|Report abuse to administrator'), }, data() { return { @@ -28,11 +29,8 @@ export default { }, }, methods: { - openDrawer() { - this.open = true; - }, - closeDrawer() { - this.open = false; + toggleDrawer(open) { + this.open = open; }, hideTooltips() { this.$root.$emit(BV_HIDE_TOOLTIP); @@ -47,9 +45,14 @@ export default { category="primary" :aria-label="buttonTooltipText" icon="error" - @click="openDrawer" + @click="toggleDrawer(true)" @mouseout="hideTooltips" /> - <abuse-category-selector :show-drawer="open" @close-drawer="closeDrawer" /> + <abuse-category-selector + :reported-user-id="reportedUserId" + :reported-from-url="reportedFromUrl" + :show-drawer="open" + @close-drawer="toggleDrawer(false)" + /> </span> </template> diff --git a/app/assets/javascripts/users/profile/index.js b/app/assets/javascripts/users/profile/index.js index 37f8e3ac471..c6b85489785 100644 --- a/app/assets/javascripts/users/profile/index.js +++ b/app/assets/javascripts/users/profile/index.js @@ -10,7 +10,12 @@ export const initReportAbuse = () => { return new Vue({ el, - provide: { reportAbusePath, reportedUserId, reportedFromUrl }, + name: 'ReportAbuseButtonRoot', + provide: { + reportAbusePath, + reportedUserId: parseInt(reportedUserId, 10), + reportedFromUrl, + }, render(createElement) { return createElement(ReportAbuseButton); }, diff --git a/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/app/assets/javascripts/vue_shared/security_reports/store/utils.js index f3cb5fc16f0..f620bad8dba 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js @@ -24,42 +24,37 @@ export const fetchDiffData = (state, endpoint, category) => { /** * Returns given vulnerability enriched with the corresponding * feedback (`dismissal` or `issue` type) - * @param {Object} vulnerability - * @param {Array} feedback + * @param {Object} vulnerabilityObject + * @param {Array} feedbackList */ -export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) => - feedback +export const enrichVulnerabilityWithFeedback = (vulnerabilityObject, feedbackList = []) => { + const vulnerability = { ...vulnerabilityObject }; + // Some records may have a null `uuid`, we need to fallback to using `project_fingerprint` in those cases. Once all entries have been fixed, we can remove the fallback. + // related epic: https://gitlab.com/groups/gitlab-org/-/epics/2791 + feedbackList .filter((fb) => - // Some records still have a `finding_uuid` with null, we need to fallback to using `project_fingerprint` in those cases. Once all entries have been fixed, we can remove the fallback. - // related epic: https://gitlab.com/groups/gitlab-org/-/epics/2791 - fb.finding_uuid !== null - ? fb.finding_uuid === vulnerability.finding_uuid + fb.finding_uuid + ? fb.finding_uuid === vulnerability.uuid : fb.project_fingerprint === vulnerability.project_fingerprint, ) - .reduce((vuln, fb) => { - if (fb.feedback_type === FEEDBACK_TYPE_DISMISSAL) { - return { - ...vuln, - isDismissed: true, - dismissalFeedback: fb, - }; + .forEach((feedback) => { + if (feedback.feedback_type === FEEDBACK_TYPE_DISMISSAL) { + vulnerability.isDismissed = true; + vulnerability.dismissalFeedback = feedback; + } else if (feedback.feedback_type === FEEDBACK_TYPE_ISSUE && feedback.issue_iid) { + vulnerability.hasIssue = true; + vulnerability.issue_feedback = feedback; + } else if ( + feedback.feedback_type === FEEDBACK_TYPE_MERGE_REQUEST && + feedback.merge_request_iid + ) { + vulnerability.hasMergeRequest = true; + vulnerability.merge_request_feedback = feedback; } - if (fb.feedback_type === FEEDBACK_TYPE_ISSUE && fb.issue_iid) { - return { - ...vuln, - hasIssue: true, - issue_feedback: fb, - }; - } - if (fb.feedback_type === FEEDBACK_TYPE_MERGE_REQUEST && fb.merge_request_iid) { - return { - ...vuln, - hasMergeRequest: true, - merge_request_feedback: fb, - }; - } - return vuln; - }, vulnerability); + }); + + return vulnerability; +}; /** * Generates the added, fixed, and existing vulnerabilities from the API report. diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index eec56682300..378da9ef146 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -19,6 +19,13 @@ class AbuseReportsController < ApplicationController reported_from_url: report_params[:reported_from_url] ) + Gitlab::Tracking.event( + 'ReportAbuse', + 'select_abuse_category', + property: report_params[:category], + user: @user + ) + render :new end @@ -29,6 +36,13 @@ class AbuseReportsController < ApplicationController if @abuse_report.save @abuse_report.notify + Gitlab::Tracking.event( + 'ReportAbuse', + 'submit_form', + property: @abuse_report.category, + user: @abuse_report.user + ) + message = _("Thank you for your report. A GitLab administrator will look into it shortly.") redirect_to root_path, notice: message elsif report_params[:user_id].present? diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ee2c268ff33..cdccd883c0d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -45,7 +45,6 @@ class ProjectsController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_mvc, @project&.work_items_mvc_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?) - push_frontend_feature_flag(:package_registry_access_level) end layout :determine_layout diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 1dcb9190f11..5c8ac2adb8d 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -18,6 +18,7 @@ module Ci belongs_to :build, class_name: 'CommitStatus' belongs_to :project + belongs_to :runner_machine, class_name: 'Ci::RunnerMachine' before_create :set_build_project diff --git a/app/models/ci/runner_machine.rb b/app/models/ci/runner_machine.rb index 1dd997a8ee1..44be14ab980 100644 --- a/app/models/ci/runner_machine.rb +++ b/app/models/ci/runner_machine.rb @@ -7,6 +7,8 @@ module Ci belongs_to :runner + has_many :build_metadata, class_name: 'Ci::BuildMetadata' + validates :runner, presence: true validates :machine_xid, presence: true, length: { maximum: 64 } validates :version, length: { maximum: 2048 } diff --git a/app/policies/packages/policies/project_policy.rb b/app/policies/packages/policies/project_policy.rb index 0fb5953f2aa..35161fd95f1 100644 --- a/app/policies/packages/policies/project_policy.rb +++ b/app/policies/packages/policies/project_policy.rb @@ -7,25 +7,10 @@ module Packages overrides(:read_package) - condition(:package_registry_access_level_feature_flag_enabled, scope: :subject) do - ::Feature.enabled?(:package_registry_access_level, @subject) - end - condition(:packages_enabled_for_everyone, scope: :subject) do @subject.package_registry_access_level == ProjectFeature::PUBLIC end - # This rule can be removed if the `package_registry_access_level` feature flag is removed. - # Reason: If the feature flag is globally enabled, this rule will never be executed. - rule { anonymous & ~project.public_project & ~package_registry_access_level_feature_flag_enabled }.prevent_all - - # This rule can be removed if the `package_registry_access_level` feature flag is removed. - # Reason: If the feature flag is globally enabled, this rule will never be executed. - rule do - ~project.public_project & ~project.internal_access & - ~project.project_allowed_for_job_token & ~package_registry_access_level_feature_flag_enabled - end.prevent_all - rule { project.packages_disabled }.policy do prevent(:read_package) end @@ -46,7 +31,7 @@ module Packages enable :read_package end - rule { package_registry_access_level_feature_flag_enabled & packages_enabled_for_everyone }.policy do + rule { packages_enabled_for_everyone }.policy do enable :read_package end end diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 40935ab6f70..c86f9c79912 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -11,4 +11,5 @@ target_type: 'issue', show_timeline_view_toggle: show_timeline_view_toggle?(@issue).to_s, current_user_data: UserSerializer.new.represent(current_user, {only_path: true}, CurrentUserEntity).to_json, - can_add_timeline_events: "#{can?(current_user, :admin_incident_management_timeline_event, @issue)}" } } + can_add_timeline_events: "#{can?(current_user, :admin_incident_management_timeline_event, @issue)}", + report_abuse_path: add_category_abuse_reports_path } } diff --git a/app/views/projects/merge_requests/_page.html.haml b/app/views/projects/merge_requests/_page.html.haml index c609c84d336..be67b317746 100644 --- a/app/views/projects/merge_requests/_page.html.haml +++ b/app/views/projects/merge_requests/_page.html.haml @@ -81,7 +81,8 @@ target_type: 'merge_request', help_page_path: suggest_changes_help_path, current_user_data: @current_user_data, - is_locked: @merge_request.discussion_locked.to_s } } + is_locked: @merge_request.discussion_locked.to_s, + report_abuse_path: add_category_abuse_reports_path } } - if moved_mr_sidebar_enabled? = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch diff --git a/config/application.rb b/config/application.rb index 61dfa59a90d..965985707d3 100644 --- a/config/application.rb +++ b/config/application.rb @@ -314,6 +314,7 @@ module Gitlab config.assets.precompile << "page_bundles/project.css" config.assets.precompile << "page_bundles/projects_edit.css" config.assets.precompile << "page_bundles/prometheus.css" + config.assets.precompile << "page_bundles/promotions.css" config.assets.precompile << "page_bundles/releases.css" config.assets.precompile << "page_bundles/reports.css" config.assets.precompile << "page_bundles/roadmap.css" diff --git a/config/feature_flags/development/package_registry_access_level.yml b/config/feature_flags/development/package_registry_access_level.yml deleted file mode 100644 index 3434e7f4260..00000000000 --- a/config/feature_flags/development/package_registry_access_level.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: package_registry_access_level -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82808 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363018 -milestone: '15.0' -type: development -group: group::package registry -default_enabled: true diff --git a/db/docs/namespaces_storage_limit_exclusions.yml b/db/docs/namespaces_storage_limit_exclusions.yml index 0a64fa6a4db..d66da2797dd 100644 --- a/db/docs/namespaces_storage_limit_exclusions.yml +++ b/db/docs/namespaces_storage_limit_exclusions.yml @@ -8,5 +8,5 @@ description: | Stores namespaces that are excluded from the storage limit. Any namespaces that are included in this table will not have storage limitations applied. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108449 -milestone: '15.8' +milestone: '15.9' gitlab_schema: gitlab_main diff --git a/db/post_migrate/20230104222438_add_partition_index_to_builds_metadata.rb b/db/post_migrate/20230104222438_add_partition_index_to_builds_metadata.rb new file mode 100644 index 00000000000..474f6f8540b --- /dev/null +++ b/db/post_migrate/20230104222438_add_partition_index_to_builds_metadata.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddPartitionIndexToBuildsMetadata < Gitlab::Database::Migration[2.1] + include Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + + INDEX_NAME = 'p_ci_builds_metadata_on_runner_machine_id_idx' + + def up + add_concurrent_partitioned_index :p_ci_builds_metadata, :runner_machine_id, name: INDEX_NAME, + where: 'runner_machine_id IS NOT NULL' + end + + def down + remove_concurrent_partitioned_index_by_name :p_ci_builds_metadata, INDEX_NAME + end +end diff --git a/db/post_migrate/20230104222514_add_foreign_key_to_builds_metadata.rb b/db/post_migrate/20230104222514_add_foreign_key_to_builds_metadata.rb new file mode 100644 index 00000000000..b9c78e4eeb9 --- /dev/null +++ b/db/post_migrate/20230104222514_add_foreign_key_to_builds_metadata.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddForeignKeyToBuildsMetadata < Gitlab::Database::Migration[2.1] + include Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + + def up + add_concurrent_partitioned_foreign_key :p_ci_builds_metadata, + :ci_runner_machines, + column: :runner_machine_id, + on_delete: :nullify + end + + def down + remove_foreign_key_if_exists :p_ci_builds_metadata, column: :runner_machine_id + end +end diff --git a/db/schema_migrations/20230104222438 b/db/schema_migrations/20230104222438 new file mode 100644 index 00000000000..9390a389376 --- /dev/null +++ b/db/schema_migrations/20230104222438 @@ -0,0 +1 @@ +4d6f00bb2679beaac6952d5324c9c3b36c54b5b7bf85fd18e57bdace9bb0ceb6
\ No newline at end of file diff --git a/db/schema_migrations/20230104222514 b/db/schema_migrations/20230104222514 new file mode 100644 index 00000000000..070e537614c --- /dev/null +++ b/db/schema_migrations/20230104222514 @@ -0,0 +1 @@ +2647722de5ee25c720772c61b516ecf6f1121a83269c53381568ce0a349750a3
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5f33b103d72..04f0b7d18bb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -28721,6 +28721,10 @@ CREATE UNIQUE INDEX index_aws_roles_on_role_external_id ON aws_roles USING btree CREATE UNIQUE INDEX index_aws_roles_on_user_id ON aws_roles USING btree (user_id); +CREATE INDEX p_ci_builds_metadata_on_runner_machine_id_idx ON ONLY p_ci_builds_metadata USING btree (runner_machine_id) WHERE (runner_machine_id IS NOT NULL); + +CREATE INDEX index_b6331cde35 ON ci_builds_metadata USING btree (runner_machine_id) WHERE (runner_machine_id IS NOT NULL); + CREATE INDEX index_background_migration_jobs_for_partitioning_migrations ON background_migration_jobs USING btree (((arguments ->> 2))) WHERE (class_name = 'Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable'::text); CREATE INDEX index_background_migration_jobs_on_class_name_and_arguments ON background_migration_jobs USING btree (class_name, arguments); @@ -33229,6 +33233,8 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p ALTER INDEX p_ci_builds_metadata_pkey ATTACH PARTITION ci_builds_metadata_pkey; +ALTER INDEX p_ci_builds_metadata_on_runner_machine_id_idx ATTACH PARTITION index_b6331cde35; + ALTER INDEX p_ci_builds_metadata_build_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts; ALTER INDEX p_ci_builds_metadata_build_id_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_and_id_and_interruptible; @@ -35904,6 +35910,9 @@ ALTER TABLE ONLY merge_requests_closing_issues ALTER TABLE ONLY banned_users ADD CONSTRAINT fk_rails_fa5bb598e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE p_ci_builds_metadata + ADD CONSTRAINT fk_rails_fae01b2700 FOREIGN KEY (runner_machine_id) REFERENCES ci_runner_machines(id) ON DELETE SET NULL; + ALTER TABLE ONLY operations_feature_flags_issues ADD CONSTRAINT fk_rails_fb4d2a7cb1 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; diff --git a/doc/development/adding_service_component.md b/doc/development/adding_service_component.md index ee14d8e6414..9b6171be9fa 100644 --- a/doc/development/adding_service_component.md +++ b/doc/development/adding_service_component.md @@ -87,7 +87,7 @@ In addition, any system dependencies used in Omnibus packages or the Cloud Nativ ## Release management -If the service component needs to be updated or released with the monthly GitLab release, then the component should be added to the [release tools automation](https://gitlab.com/gitlab-org/release-tools). This project is maintained by the [Delivery group](https://about.gitlab.com/handbook/engineering/infrastructure/team/delivery/). +If the service component needs to be updated or released with the monthly GitLab release, then the component should be added to the [release tools automation](https://gitlab.com/gitlab-org/release-tools). This project is maintained by the [Delivery group](https://about.gitlab.com/handbook/engineering/infrastructure/team/delivery/). There are different levels of automation available to include a component in GitLab releases. The requirements and process for including a component in a release at these different levels are detailed in the [release documentation](https://gitlab.com/gitlab-org/release/docs/-/tree/master/components). diff --git a/doc/development/documentation/testing.md b/doc/development/documentation/testing.md index 58584b5168b..4f61b2424eb 100644 --- a/doc/development/documentation/testing.md +++ b/doc/development/documentation/testing.md @@ -13,7 +13,7 @@ processes similar to those used for code to maintain standards and quality of do We have tests: - To lint the words and structure of the documentation. -- To check the validity of internal links within the documentation suite. +- To check the validity of internal links in the documentation suite. - To check the validity of links from UI elements, such as files in `app/views` files. For the specifics of each test run in our CI/CD pipelines, see the configuration for those tests @@ -51,7 +51,7 @@ To run tests locally, it's important to: Lint checks are performed by the [`lint-doc.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/lint-doc.sh) script and can be executed as follows: -1. Navigate to the `gitlab` directory. +1. Go to the `gitlab` directory. 1. Run: ```shell @@ -113,7 +113,7 @@ Even if a specific broken link appears multiple times on a page, the test report To execute documentation link tests locally: -1. Navigate to the [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory. +1. Go to the [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory. 1. Run the following commands: ```shell @@ -243,7 +243,9 @@ This configuration is also used in build pipelines. You can use markdownlint: -- [On the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--). +- On the command line, with either: + - [`markdownlint-cli`](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli). + - [`markdownlint-cli2`](https://github.com/DavidAnson/markdownlint-cli2#markdownlint-cli2). - [In a code editor](#configure-editors). - [In a `pre-push` hook](#configure-pre-push-hooks). @@ -281,7 +283,7 @@ You can use Vale: Vale returns three types of results: - **Error** - For branding guidelines, trademark guidelines, and anything that causes content on - the docs site to render incorrectly. + the documentation site to render incorrectly. - **Warning** - For general style guide rules, tenets, and best practices. - **Suggestion** - For technical writing style preferences that may require refactoring of documentation or updates to an exceptions list. @@ -366,23 +368,34 @@ In general, follow these guidelines: ### Install linters -At a minimum, install [markdownlint](#markdownlint) and [Vale](#vale) to match the checks run in -build pipelines: +At a minimum, install [markdownlint](#markdownlint) and [Vale](#vale) to match the checks run in build pipelines. -1. Install `markdownlint-cli`: +#### Install markdownlint - ```shell - yarn global add markdownlint-cli - ``` +You can install either `markdownlint-cli` or `markdownlint-cli2` to run `markdownlint`. + +To install `markdownlint-cli`, run: + +```shell +yarn global add markdownlint-cli +``` + +To install `markdownlint-cli2`, run: - We recommend installing the version of `markdownlint-cli` - [used (see `variables:` section)](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/.gitlab-ci.yml) when building - the `image:docs-lint-markdown`. +```shell +yarn global add markdownlint-cli2 +``` + +You should install the version of `markdownlint-cli` or `markdownlint-cli2` +[used (see `variables:` section)](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/.gitlab-ci.yml) when building +the `image:docs-lint-markdown`. + +#### Install Vale -1. Install [`vale`](https://github.com/errata-ai/vale/releases). To install for: +To install Install [`vale`](https://github.com/errata-ai/vale/releases) for: - - macOS using `brew`, run: `brew install vale`. - - Linux, use your distribution's package manager or a [released binary](https://github.com/errata-ai/vale/releases). +- macOS using `brew`, run: `brew install vale`. +- Linux, use your distribution's package manager or a [released binary](https://github.com/errata-ai/vale/releases). These tools can be [integrated with your code editor](#configure-editors). @@ -391,16 +404,18 @@ These tools can be [integrated with your code editor](#configure-editors). It's important to use linter versions that are the same or newer than those run in CI/CD. This provides access to new features and possible bug fixes. -To match the versions of `markdownlint-cli` and `vale` used in the GitLab projects, refer to the +To match the versions of `markdownlint-cli` (or `markdownlint-cli2`) and `vale` used in the GitLab projects, refer to the [versions used (see `variables:` section)](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/.gitlab-ci.yml) when building the `image:docs-lint-markdown` Docker image containing these tools for CI/CD. -| Tool | Version | Command | Additional information | -|--------------------|-----------|--------------------------------------------------------|------------------------| -| `markdownlint-cli` | Latest | `yarn global add markdownlint-cli` | None. | -| `markdownlint-cli` | Specific | `yarn global add markdownlint-cli@0.23.2` | The `@` indicates a specific version, and this example updates the tool to version `0.23.2`. | -| Vale | Latest | `brew update && brew upgrade vale` | This command is for macOS only. | -| Vale | Specific | Not applicable. | Binaries can be [directly downloaded](https://github.com/errata-ai/vale/releases). | +| Tool | Version | Command | Additional information | +|:--------------------|:---------|:------------------------------------------|:---------------------------------------------------------------------------------------------| +| `markdownlint-cli` | Latest | `yarn global add markdownlint-cli` | None. | +| `markdownlint-cli2` | Latest | `yarn global add markdownlint-cli2` | None. | +| `markdownlint-cli` | Specific | `yarn global add markdownlint-cli@0.23.2` | The `@` indicates a specific version, and this example updates the tool to version `0.23.2`. | +| `markdownlint-cli` | Specific | `yarn global add markdownlint-cli@0.6.0` | The `@` indicates a specific version, and this example updates the tool to version `0.6.0`. | +| Vale | Latest | `brew update && brew upgrade vale` | This command is for macOS only. | +| Vale | Specific | Not applicable. | Binaries can be [directly downloaded](https://github.com/errata-ai/vale/releases). | ### Configure editors @@ -410,6 +425,15 @@ command line. To configure markdownlint in your editor, install one of the following as appropriate: - Sublime Text [`SublimeLinter-contrib-markdownlint` package](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint). + This package uses `markdownlint-cli` by default, but can be configured to use `markdownlint-cli2` with this + SublimeLinter configuration: + + ```json + "markdownlint": { + "executable": [ "markdownlint-cli2" ] + } + ``` + - Visual Studio Code [`DavidAnson.vscode-markdownlint` extension](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint). - Vim [ALE plugin](https://github.com/dense-analysis/ale). @@ -463,7 +487,7 @@ Git [pre-push hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) a - Avoid pushing a branch if failures occur with these tests. [`lefthook`](https://github.com/Arkweid/lefthook) is a Git hooks manager, making configuring, -installing, and removing Git hooks easy. +installing, and removing Git hooks simpler. Configuration for `lefthook` is available in the [`lefthook.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lefthook.yml) file for the [`gitlab`](https://gitlab.com/gitlab-org/gitlab) project. @@ -509,7 +533,7 @@ You can set Visual Studio Code to display only a subset of Vale alerts when view To display only a subset of Vale alerts when running Vale from the command line, use the `--minAlertLevel` flag, which accepts `error`, `warning`, or `suggestion`. Combine it with `--config` -to point to the configuration file within the project, if needed: +to point to the configuration file in the project, if needed: ```shell vale --config .vale.ini --minAlertLevel error doc/**/*.md diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md index d0a208c995b..47badae7472 100644 --- a/doc/operations/error_tracking.md +++ b/doc/operations/error_tracking.md @@ -146,7 +146,7 @@ settings. By using a GitLab-provided DSN, your application connects to GitLab to Those errors are stored in the GitLab database and rendered by the GitLab UI, in the same way as Sentry integration. -In GitLab 15.6 and later, the integrated error tracking +In GitLab 15.6 and later, the integrated error tracking uses a new backend based on the ClickHouse database that enables better scalability. Integrated error tracking remains limited in comparison to the Sentry backend, as only a small subset of the Sentry API is implemented. diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md index e5114b092da..389bad87844 100644 --- a/doc/subscriptions/self_managed/index.md +++ b/doc/subscriptions/self_managed/index.md @@ -492,7 +492,7 @@ and for communicating directly with the relevant GitLab team members. ### Subscription data fails to synchronize -If the synchronization job is not working, ensure you allow network traffic from your GitLab +If the synchronization job is not working, ensure you allow network traffic from your GitLab instance to IP addresses `172.64.146.11:443` and `104.18.41.245:443` (`customers.gitlab.com`). ### Credit card declined diff --git a/doc/topics/gitlab_flow.md b/doc/topics/gitlab_flow.md index 89ab5e1cf2e..1735810c3cc 100644 --- a/doc/topics/gitlab_flow.md +++ b/doc/topics/gitlab_flow.md @@ -113,7 +113,7 @@ While this is possible in some cases, such as SaaS applications, there are some operations team is at full capacity - but you also merge code at other times. In these cases, you can create a production branch that reflects the deployed code. -You can deploy a new version by merging `main` into the `production` branch. +You can deploy a new version by merging `main` into the `production` branch. While not shown in the graph below, the work on the `main` branch works just like in GitHub flow, i.e. with feature-branches being merged into `main`. ```mermaid diff --git a/doc/user/analytics/code_review_analytics.md b/doc/user/analytics/code_review_analytics.md index 86f1e24429b..fae9545003f 100644 --- a/doc/user/analytics/code_review_analytics.md +++ b/doc/user/analytics/code_review_analytics.md @@ -10,19 +10,19 @@ info: To determine the technical writer assigned to the Stage/Group associated w > Moved to GitLab Premium in 13.9. -Use code review analytics to view review metrics per merge request and -make improvements to your code review process: +Code review analytics displays a table of open merge requests that have at least one non-author comment. +The review time is the amount of time since the first comment by a non-author in a merge request. + +You can use code review analytics to view review metrics per merge request +and improve your code review process. - A high number of comments or commits may indicate: - - The code is too complex. + - Code that is too complex. - Authors who require more training. - A long review time may indicate: - Types of work that move slower than other types. - Opportunities to accelerate your development cycle. -- Fewer comments and approvers may indicate staffing requirements. - -Code review analytics displays a table of open merge requests that have at least one non-author comment. -The review time is measured from when the first non-author comment was submitted. +- Few comments and approvers may indicate a lack of available team members. ## View code review analytics @@ -34,4 +34,18 @@ To view code review analytics: 1. On the top bar, select **Main menu > Projects** and find your project. 1. On the left sidebar, select **Analytics > Code Review**. -1. Filter merge requests by milestone and label. +1. Optional. Filter results: + 1. Select the filter bar. + 1. Select a parameter. You can filter merge requests by milestone and label. + 1. Select a value for the selected parameter. + +The table shows up to 20 merge requests in review per page, +and includes the following information about each merge request: + +- Merge request title +- Review time +- Author +- Approvers +- Comments +- Commits +- Line changes diff --git a/doc/user/application_security/terminology/index.md b/doc/user/application_security/terminology/index.md index 2666d91c5aa..cdc70a73f87 100644 --- a/doc/user/application_security/terminology/index.md +++ b/doc/user/application_security/terminology/index.md @@ -91,7 +91,7 @@ applications, and infrastructure. Findings are all potential vulnerability items scanners identify in MRs/feature branches. Only after merging to default does a finding become a [vulnerability](#vulnerability). -You can interact with vulnerability findings in two ways. +You can interact with vulnerability findings in two ways. 1. You can open an issue or merge request for the vulnerability finding. 1. You can dismiss the vulnerability finding. Dismissing the finding hides it from the default views. diff --git a/doc/user/group/compliance_frameworks.md b/doc/user/group/compliance_frameworks.md index 9f40f9e84bf..e082aed6d57 100644 --- a/doc/user/group/compliance_frameworks.md +++ b/doc/user/group/compliance_frameworks.md @@ -25,8 +25,8 @@ Group owners can create, edit, and delete compliance frameworks: > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/375036) in GitLab 15.6. -Group owners can set a default compliance framework. The default framework is applied to all the new and imported -projects that are created within that group. It does not affect the framework applied to the existing projects. The +Group owners can set a default compliance framework. The default framework is applied to all the new and imported +projects that are created within that group. It does not affect the framework applied to the existing projects. The default framework cannot be deleted. A compliance framework that is set to default has a **default** label. diff --git a/doc/user/markdown.md b/doc/user/markdown.md index d29369f142c..66f9f0594bf 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -353,7 +353,7 @@ _KaTeX only supports a [subset](https://katex.org/docs/supported.html) of LaTeX. This syntax also works for the Asciidoctor `:stem: latexmath`. For details, see the [Asciidoctor user manual](https://asciidoctor.org/docs/user-manual/#activating-stem-support). -Math written between dollar signs with backticks (``$`...`$``) or single dollar signs (`$...$`) +Math written between dollar signs with backticks (``$`...`$``) or single dollar signs (`$...$`) is rendered inline with the text. Math written between double dollar signs (`$$...$$`) or in a [code block](#code-spans-and-blocks) with diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 006243bb9ec..991a38f1ddd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -33189,9 +33189,6 @@ msgstr "" msgid "ProjectSettings|Every project can have its own space to store its Docker images" msgstr "" -msgid "ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public." -msgstr "" - msgid "ProjectSettings|Every project can make deployments to environments either via CI/CD or API calls. Non-project members have read-only access." msgstr "" @@ -33324,9 +33321,6 @@ msgstr "" msgid "ProjectSettings|Package registry" msgstr "" -msgid "ProjectSettings|Packages" -msgstr "" - msgid "ProjectSettings|Pages" msgstr "" @@ -35416,13 +35410,13 @@ msgstr "" msgid "ReportAbuse|Something else." msgstr "" -msgid "ReportAbuse|They're being offsensive or abusive." +msgid "ReportAbuse|They're being offensive or abusive." msgstr "" msgid "ReportAbuse|They're crypto mining." msgstr "" -msgid "ReportAbuse|They're phising." +msgid "ReportAbuse|They're phishing." msgstr "" msgid "ReportAbuse|They're posting malware." diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index 68dfac95ef6..6fb5dfbd6e8 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -128,7 +128,7 @@ function run_locally_or_in_docker() { $cmd $args elif hash docker 2>/dev/null then - docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2 ${cmd} ${args} + docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.16-vale-2.22.0-markdownlint-0.32.2-markdownlint2-0.6.0 ${cmd} ${args} else echo echo " ✖ ERROR: '${cmd}' not found. Install '${cmd}' or Docker to proceed." >&2 diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 4c2383292c7..4af901e4cf6 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -39,7 +39,7 @@ RSpec.describe 'Database schema', feature_category: :database do ci_build_trace_metadata: %w[partition_id], ci_builds: %w[erased_by_id trigger_request_id partition_id], ci_builds_runner_session: %w[partition_id], - p_ci_builds_metadata: %w[partition_id runner_machine_id], # NOTE: FK will be added in follow-up https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108167 + p_ci_builds_metadata: %w[partition_id], ci_job_artifacts: %w[partition_id], ci_job_variables: %w[partition_id], ci_namespace_monthly_usages: %w[namespace_id], diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index e0a61656a88..31b86244dd1 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -82,7 +82,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do visit user_path(abusive_user) - fill_and_submit_abuse_category_form("They're being offsensive or abusive.") + fill_and_submit_abuse_category_form("They're being offensive or abusive.") fill_and_submit_report_abuse_form expect(page).to have_content 'Thank you for your report' @@ -136,7 +136,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do click_button 'More actions' end - it_behaves_like 'reports the user without an abuse category' + it_behaves_like 'reports the user with an abuse category' end end diff --git a/spec/features/projects/settings/packages_settings_spec.rb b/spec/features/projects/settings/packages_settings_spec.rb index 4ef17830f81..bf5c779b109 100644 --- a/spec/features/projects/settings/packages_settings_spec.rb +++ b/spec/features/projects/settings/packages_settings_spec.rb @@ -11,7 +11,6 @@ RSpec.describe 'Projects > Settings > Packages', :js, feature_category: :project sign_in(user) stub_config(packages: { enabled: packages_enabled }) - stub_feature_flags(package_registry_access_level: package_registry_access_level) visit edit_project_path(project) end @@ -19,35 +18,21 @@ RSpec.describe 'Projects > Settings > Packages', :js, feature_category: :project context 'Packages enabled in config' do let(:packages_enabled) { true } - context 'with feature flag disabled' do - let(:package_registry_access_level) { false } - - it 'displays the packages toggle button' do - expect(page).to have_selector('[data-testid="toggle-label"]', text: 'Packages') - expect(page).to have_selector('input[name="project[packages_enabled]"] + button', visible: true) - end - end - - context 'with feature flag enabled' do - let(:package_registry_access_level) { true } - - it 'displays the packages access level setting' do - expect(page).to have_selector('[data-testid="package-registry-access-level"] > label', text: 'Package registry') - expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false) - expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true) - expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false) - expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true) - expect(page).to have_selector( - 'input[name="project[project_feature_attributes][package_registry_access_level]"]', - visible: false - ) - end + it 'displays the packages access level setting' do + expect(page).to have_selector('[data-testid="package-registry-access-level"] > label', text: 'Package registry') + expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false) + expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true) + expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false) + expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true) + expect(page).to have_selector( + 'input[name="project[project_feature_attributes][package_registry_access_level]"]', + visible: false + ) end end context 'Packages disabled in config' do let(:packages_enabled) { false } - let(:package_registry_access_level) { false } it 'does not show up in UI' do expect(page).not_to have_selector('[data-testid="toggle-label"]', text: 'Packages') diff --git a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js index 6efd9fb1dd0..3383a5c86ec 100644 --- a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js +++ b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js @@ -13,18 +13,18 @@ describe('AbuseCategorySelector', () => { let wrapper; const ACTION_PATH = '/abuse_reports/add_category'; - const USER_ID = '1'; + const USER_ID = 1; const REPORTED_FROM_URL = 'http://example.com'; const createComponent = (props) => { wrapper = shallowMountExtended(AbuseCategorySelector, { propsData: { + reportedUserId: USER_ID, + reportedFromUrl: REPORTED_FROM_URL, ...props, }, provide: { reportAbusePath: ACTION_PATH, - reportedUserId: USER_ID, - reportedFromUrl: REPORTED_FROM_URL, }, }); }; @@ -106,7 +106,7 @@ describe('AbuseCategorySelector', () => { expect(findUserId().attributes()).toMatchObject({ type: 'hidden', name: 'user_id', - value: USER_ID, + value: `${USER_ID}`, }); }); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js index cc1e5de15c1..bc66a0515aa 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js @@ -1,4 +1,4 @@ -import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui'; +import { GlAlert, GlFormInput, GlForm, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; @@ -11,12 +11,14 @@ const TEST_ISSUE_A = { iid: 8, title: 'Issue 1', referencePath: 'h/b#1', + webUrl: 'webUrl', }; const TEST_ISSUE_B = { id: 'gid://gitlab/Issue/2', iid: 9, title: 'Issue 2', referencePath: 'h/b#2', + webUrl: 'webUrl', }; describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { @@ -49,6 +51,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { const findForm = () => wrapper.findComponent(GlForm); const findAlert = () => wrapper.findComponent(GlAlert); const findFormInput = () => wrapper.findComponent(GlFormInput); + const findGlLink = () => wrapper.findComponent(GlLink); const findEditableItem = () => wrapper.findComponent(BoardEditableItem); const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]'); const findTitle = () => wrapper.find('[data-testid="item-title"]'); @@ -67,6 +70,12 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { expect(findAlert().exists()).toBe(false); }); + it('links title to the corresponding issue', () => { + createWrapper(); + + expect(findGlLink().attributes('href')).toBe('webUrl'); + }); + describe('when new title is submitted', () => { beforeEach(async () => { createWrapper(); diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js index aaf228ae181..8a0fdcb50d0 100644 --- a/spec/frontend/issues/show/components/header_actions_spec.js +++ b/spec/frontend/issues/show/components/header_actions_spec.js @@ -40,7 +40,7 @@ describe('HeaderActions component', () => { newIssuePath: 'gitlab-org/gitlab-test/-/issues/new', projectPath: 'gitlab-org/gitlab-test', reportAbusePath: '-/abuse_reports/add_category', - reportedUserId: '1', + reportedUserId: 1, reportedFromUrl: 'http://localhost:/gitlab-org/-/issues/32', submitAsSpamPath: 'gitlab-org/gitlab-test/-/issues/32/submit_as_spam', }; diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js index c7420ca9c48..d95e357f24c 100644 --- a/spec/frontend/notes/components/note_actions_spec.js +++ b/spec/frontend/notes/components/note_actions_spec.js @@ -7,6 +7,7 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import noteActions from '~/notes/components/note_actions.vue'; import { NOTEABLE_TYPE_MAPPING } from '~/notes/constants'; import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue'; +import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import createStore from '~/notes/stores'; import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue'; import { userDataMock } from '../mock_data'; @@ -21,6 +22,7 @@ describe('noteActions', () => { const findUserAccessRoleBadge = (idx) => wrapper.findAllComponents(UserAccessRoleBadge).at(idx); const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim(); const findTimelineButton = () => wrapper.findComponent(TimelineEventButton); + const findReportAbuseButton = () => wrapper.find(`[data-testid="report-abuse-button"]`); const setupStoreForIncidentTimelineEvents = ({ userCanAdd, @@ -63,7 +65,6 @@ describe('noteActions', () => { noteId: '539', noteUrl: `${TEST_HOST}/group/project/-/merge_requests/1#note_1`, projectName: 'project', - reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26`, showReply: false, awardPath: `${TEST_HOST}/award_emoji`, }; @@ -115,7 +116,7 @@ describe('noteActions', () => { }); it('should be possible to report abuse to admin', () => { - expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true); + expect(findReportAbuseButton().exists()).toBe(true); }); it('should be possible to copy link to a note', () => { @@ -373,4 +374,57 @@ describe('noteActions', () => { }); }); }); + + describe('report abuse button', () => { + const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector); + + describe('when user is not allowed to report abuse', () => { + beforeEach(() => { + store.dispatch('setUserData', userDataMock); + wrapper = mountNoteActions({ ...props, canReportAsAbuse: false }); + }); + + it('does not render the report abuse', () => { + expect(findReportAbuseButton().exists()).toBe(false); + }); + + it('does not render the abuse category drawer', () => { + expect(findAbuseCategorySelector().exists()).toBe(false); + }); + }); + + describe('when user is allowed to report abuse', () => { + beforeEach(() => { + store.dispatch('setUserData', userDataMock); + wrapper = mountNoteActions({ ...props, canReportAsAbuse: true }); + }); + + it('renders report abuse button', () => { + expect(findReportAbuseButton().exists()).toBe(true); + }); + + it('renders abuse category drawer', () => { + expect(findAbuseCategorySelector().exists()).toBe(true); + expect(findAbuseCategorySelector().props()).toMatchObject({ + showDrawer: false, + reportedUserId: props.authorId, + reportedFromUrl: props.noteUrl, + }); + }); + + it('opens the drawer when report abuse button is clicked', async () => { + findReportAbuseButton().trigger('click'); + + await nextTick(); + + expect(findAbuseCategorySelector().props('showDrawer')).toEqual(true); + }); + + it('closes the drawer', async () => { + await findAbuseCategorySelector().vm.$emit('close-drawer'); + + expect(findAbuseCategorySelector().props('showDrawer')).toEqual(false); + }); + }); + }); }); diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index 3d7195752d3..af1b4f64037 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -34,6 +34,9 @@ const singleLineNotePosition = { describe('issue_note', () => { let store; let wrapper; + + const REPORT_ABUSE_PATH = '/abuse_reports/add_category'; + const findMultilineComment = () => wrapper.find('[data-testid="multiline-comment"]'); const createWrapper = (props = {}, storeUpdater = (s) => s) => { @@ -62,6 +65,9 @@ describe('issue_note', () => { 'note-body', 'multiline-comment-form', ], + provide: { + reportAbusePath: REPORT_ABUSE_PATH, + }, }); }; @@ -241,7 +247,6 @@ describe('issue_note', () => { expect(noteActionsProps.canDelete).toBe(note.current_user.can_edit); expect(noteActionsProps.canReportAsAbuse).toBe(true); expect(noteActionsProps.canResolve).toBe(false); - expect(noteActionsProps.reportAbusePath).toBe(note.report_abuse_path); expect(noteActionsProps.resolvable).toBe(false); expect(noteActionsProps.isResolved).toBe(false); expect(noteActionsProps.isResolving).toBe(false); diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js index 93c2196b210..689b53fa2a4 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js @@ -4,36 +4,38 @@ import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash'; -import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; -import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql'; +import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; import { - packageDestroyMutation, - packageDestroyMutationError, + packagesDestroyMutation, + packagesDestroyMutationError, packagesListQuery, } from '../../mock_data'; jest.mock('~/flash'); -describe('DeletePackage', () => { +describe('DeletePackages', () => { let wrapper; let apolloProvider; let resolver; let mutationResolver; - const eventPayload = { id: '1' }; + const eventPayload = [{ id: '1' }]; + const eventPayloadMultiple = [{ id: '1' }, { id: '2' }]; + const mutationPayload = { ids: ['1'] }; function createComponent(propsData = {}) { Vue.use(VueApollo); const requestHandlers = [ [getPackagesQuery, resolver], - [destroyPackageMutation, mutationResolver], + [destroyPackagesMutation, mutationResolver], ]; apolloProvider = createMockApollo(requestHandlers); - wrapper = shallowMountExtended(DeletePackage, { + wrapper = shallowMountExtended(DeletePackages, { propsData, apolloProvider, scopedSlots: { @@ -43,7 +45,9 @@ describe('DeletePackage', () => { 'data-testid': 'trigger-button', }, on: { - click: props.deletePackage, + click: (payload) => { + return props.deletePackages(payload[0]); + }, }, }); }, @@ -54,23 +58,23 @@ describe('DeletePackage', () => { const findButton = () => wrapper.findByTestId('trigger-button'); const clickOnButtonAndWait = (payload) => { - findButton().trigger('click', payload); + findButton().trigger('click', [payload]); return waitForPromises(); }; beforeEach(() => { resolver = jest.fn().mockResolvedValue(packagesListQuery()); - mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation()); + mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation()); }); afterEach(() => { wrapper.destroy(); }); - it('binds deletePackage method to the default slot', () => { + it('binds deletePackages method to the default slot', () => { createComponent(); - findButton().trigger('click'); + findButton().trigger('click', eventPayload); expect(wrapper.emitted('start')).toEqual([[]]); }); @@ -80,7 +84,7 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); - expect(mutationResolver).toHaveBeenCalledWith(eventPayload); + expect(mutationResolver).toHaveBeenCalledWith(mutationPayload); }); it('passes refetchQueries to apollo mutate', async () => { @@ -91,10 +95,20 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); - expect(mutationResolver).toHaveBeenCalledWith(eventPayload); + expect(mutationResolver).toHaveBeenCalledWith(mutationPayload); expect(resolver).toHaveBeenCalledWith(variables); }); + describe('when payload contains multiple packages', () => { + it('calls apollo mutation with different payload', async () => { + createComponent(); + + await clickOnButtonAndWait(eventPayloadMultiple); + + expect(mutationResolver).toHaveBeenCalledWith({ ids: ['1', '2'] }); + }); + }); + describe('on mutation success', () => { it('emits end event', async () => { createComponent(); @@ -118,16 +132,29 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); expect(createAlert).toHaveBeenCalledWith({ - message: DeletePackage.i18n.successMessage, + message: DeletePackages.i18n.successMessage, variant: VARIANT_SUCCESS, }); }); + + describe('when payload contains multiple packages', () => { + it('calls createAlert with success message when showSuccessAlert is true', async () => { + createComponent({ showSuccessAlert: true }); + + await clickOnButtonAndWait(eventPayloadMultiple); + + expect(createAlert).toHaveBeenCalledWith({ + message: DeletePackages.i18n.successMessageMultiple, + variant: VARIANT_SUCCESS, + }); + }); + }); }); describe.each` errorType | mutationResolverResponse ${'connectionError'} | ${jest.fn().mockRejectedValue()} - ${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())} + ${'localError'} | ${jest.fn().mockResolvedValue(packagesDestroyMutationError())} `('on mutation $errorType', ({ mutationResolverResponse }) => { beforeEach(() => { mutationResolver = mutationResolverResponse; @@ -147,11 +174,26 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); expect(createAlert).toHaveBeenCalledWith({ - message: DeletePackage.i18n.errorMessage, + message: DeletePackages.i18n.errorMessage, variant: VARIANT_WARNING, captureError: true, error: expect.any(Error), }); }); + + describe('when payload contains multiple packages', () => { + it('calls createAlert with error message', async () => { + createComponent({ showSuccessAlert: true }); + + await clickOnButtonAndWait(eventPayloadMultiple); + + expect(createAlert).toHaveBeenCalledWith({ + message: DeletePackages.i18n.errorMessageMultiple, + variant: VARIANT_WARNING, + captureError: true, + error: expect.any(Error), + }); + }); + }); }); }); 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 5e9cb8fbb0b..610640e0ca3 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 @@ -167,8 +167,8 @@ describe('packages_list', () => { findPackageListDeleteModal().vm.$emit('ok'); }); - it('emits package:delete when modal confirms', () => { - expect(wrapper.emitted('package:delete')[0]).toEqual([firstPackage]); + it('emits delete when modal confirms', () => { + expect(wrapper.emitted('delete')[0][0]).toEqual([firstPackage]); }); it('tracks the right action', () => { 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 9e9e08bc196..8c532f31499 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -294,14 +294,6 @@ export const packageMetadataQuery = (packageType) => { }; }; -export const packageDestroyMutation = () => ({ - data: { - destroyPackage: { - errors: [], - }, - }, -}); - export const packagesDestroyMutation = () => ({ data: { destroyPackages: { @@ -329,25 +321,6 @@ export const packagesDestroyMutationError = () => ({ ], }); -export const packageDestroyMutationError = () => ({ - data: { - destroyPackage: 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: ['destroyPackage'], - }, - ], -}); - export const packageDestroyFilesMutation = () => ({ data: { destroyPackageFiles: { diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index eb3b999c1ca..c6a770fad76 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -15,7 +15,7 @@ import InstallationCommands from '~/packages_and_registries/package_registry/com import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; -import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import { FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, @@ -85,7 +85,7 @@ describe('PackagesApp', () => { provide, stubs: { PackageTitle, - DeletePackage, + DeletePackages, GlModal: { template: ` <div> @@ -128,7 +128,7 @@ describe('PackagesApp', () => { const findDependenciesCountBadge = () => wrapper.findByTestId('dependencies-badge'); const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message'); const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); - const findDeletePackage = () => wrapper.findComponent(DeletePackage); + const findDeletePackages = () => wrapper.findComponent(DeletePackages); afterEach(() => { wrapper.destroy(); @@ -267,7 +267,7 @@ describe('PackagesApp', () => { await waitForPromises(); - findDeletePackage().vm.$emit('end'); + findDeletePackages().vm.$emit('end'); expect(window.location.replace).toHaveBeenCalledWith( 'projectListUrl?showSuccessDeleteAlert=true', @@ -281,7 +281,7 @@ describe('PackagesApp', () => { await waitForPromises(); - findDeletePackage().vm.$emit('end'); + findDeletePackages().vm.$emit('end'); expect(window.location.replace).toHaveBeenCalledWith( 'groupListUrl?showSuccessDeleteAlert=true', 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 b3cbd9f5dcf..a2ec527ce12 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 { GlAlert, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -8,26 +8,18 @@ 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 DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import { PROJECT_RESOURCE_TYPE, GROUP_RESOURCE_TYPE, GRAPHQL_PAGE_SIZE, 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 destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; -import { - packagesListQuery, - packageData, - pagination, - packagesDestroyMutation, - packagesDestroyMutationError, -} from '../mock_data'; +import { packagesListQuery, packageData, pagination } from '../mock_data'; jest.mock('~/flash'); @@ -53,12 +45,11 @@ describe('PackagesListApp', () => { filters: { packageName: 'foo', packageType: 'CONAN' }, }; - const findAlert = () => wrapper.findComponent(GlAlert); 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 findDeletePackages = () => wrapper.findComponent(DeletePackages); const mountComponent = ({ resolver = jest.fn().mockResolvedValue(packagesListQuery()), @@ -82,7 +73,7 @@ describe('PackagesListApp', () => { GlSprintf, GlLink, PackageList, - DeletePackage, + DeletePackages, }, }); }; @@ -243,26 +234,26 @@ describe('PackagesListApp', () => { }); }); - describe('delete package', () => { + describe('delete packages', () => { it('exists and has the correct props', async () => { mountComponent(); await waitForFirstRequest(); - expect(findDeletePackage().props()).toMatchObject({ + expect(findDeletePackages().props()).toMatchObject({ refetchQueries: [{ query: getPackagesQuery, variables: {} }], showSuccessAlert: true, }); }); - it('deletePackage is bound to package-list package:delete event', async () => { + it('deletePackages is bound to package-list delete event', async () => { mountComponent(); await waitForFirstRequest(); - findListComponent().vm.$emit('package:delete', { id: 1 }); + findListComponent().vm.$emit('delete', [{ id: 1 }]); - expect(findDeletePackage().emitted('start')).toEqual([[]]); + expect(findDeletePackages().emitted('start')).toEqual([[]]); }); it('start and end event set loading correctly', async () => { @@ -270,59 +261,17 @@ describe('PackagesListApp', () => { await waitForFirstRequest(); - findDeletePackage().vm.$emit('start'); + findDeletePackages().vm.$emit('start'); await nextTick(); expect(findListComponent().props('isLoading')).toBe(true); - findDeletePackage().vm.$emit('end'); + findDeletePackages().vm.$emit('end'); await nextTick(); expect(findListComponent().props('isLoading')).toBe(false); }); }); - - describe('bulk delete package', () => { - const items = [{ id: '1' }, { id: '2' }]; - - 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); - - 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); - - 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/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js index 38f7a2e919d..ff20b72c72c 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js +++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js @@ -75,10 +75,7 @@ describe('Settings Panel', () => { return mountFn(settingsPanel, { propsData, provide: { - glFeatures: { - packageRegistryAccessLevel: false, - ...glFeatures, - }, + glFeatures, }, stubs, }); @@ -110,10 +107,8 @@ describe('Settings Panel', () => { findContainerRegistrySettings().findComponent(GlSprintf); const findContainerRegistryAccessLevelInput = () => wrapper.find('[name="project[project_feature_attributes][container_registry_access_level]"]'); - const findPackageSettings = () => wrapper.findComponent({ ref: 'package-settings' }); const findPackageAccessLevel = () => wrapper.find('[data-testid="package-registry-access-level"]'); - const findPackagesEnabledInput = () => wrapper.find('[name="project[packages_enabled]"]'); const findPackageRegistryEnabledInput = () => wrapper.find('[name="package_registry_enabled"]'); const findPackageRegistryAccessLevelHiddenInput = () => wrapper.find( @@ -512,180 +507,108 @@ describe('Settings Panel', () => { }); describe('Packages', () => { - it('should show the packages settings if packages are available', () => { - wrapper = mountComponent({ packagesAvailable: true }); - - expect(findPackageSettings().exists()).toBe(true); - }); - - it('should hide the packages settings if packages are not available', () => { - wrapper = mountComponent({ packagesAvailable: false }); + it('should hide the package access level settings with packagesAvailable = false', () => { + wrapper = mountComponent(); - expect(findPackageSettings().exists()).toBe(false); + expect(findPackageAccessLevel().exists()).toBe(false); }); - it('should set the package settings help path', () => { + it('renders the package access level settings with packagesAvailable = true', () => { wrapper = mountComponent({ packagesAvailable: true }); - expect(findPackageSettings().props('helpPath')).toBe(defaultProps.packagesHelpPath); + expect(findPackageAccessLevel().exists()).toBe(true); }); - it('should enable the packages input when the repository is enabled', () => { - wrapper = mountComponent({ - currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE }, - packagesAvailable: true, - }); - - expect(findPackagesEnabledInput().props('disabled')).toBe(false); - }); - - it('should disable the packages input when the repository is disabled', () => { - wrapper = mountComponent({ - currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED }, - packagesAvailable: true, - }); - - expect(findPackagesEnabledInput().props('disabled')).toBe(true); - }); - - it('has label for toggle', () => { - wrapper = mountComponent({ - currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE }, - packagesAvailable: true, - }); - - expect(findPackagesEnabledInput().findComponent(GlToggle).props('label')).toBe( - settingsPanel.i18n.packagesLabel, - ); - }); - - it('should hide the package access level settings', () => { - wrapper = mountComponent(); + it('has hidden input field for package registry access level', () => { + wrapper = mountComponent({ packagesAvailable: true }); - expect(findPackageAccessLevel().exists()).toBe(false); + expect(findPackageRegistryAccessLevelHiddenInput().exists()).toBe(true); }); - describe('packageRegistryAccessLevel feature flag = true', () => { - it('should hide the packages settings', () => { + it.each` + projectVisibilityLevel | packageRegistryEnabled | packageRegistryApiForEveryoneEnabled | expectedAccessLevel + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${false} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${false} | ${featureAccessLevel.EVERYONE} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${'hidden'} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${'hidden'} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + `( + 'sets correct access level', + async ({ + projectVisibilityLevel, + packageRegistryEnabled, + packageRegistryApiForEveryoneEnabled, + expectedAccessLevel, + }) => { wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, packagesAvailable: true, + currentSettings: { + visibilityLevel: projectVisibilityLevel, + }, }); - expect(findPackageSettings().exists()).toBe(false); - }); - - it('should hide the package access level settings with packagesAvailable = false', () => { - wrapper = mountComponent({ glFeatures: { packageRegistryAccessLevel: true } }); + await findPackageRegistryEnabledInput().vm.$emit('change', packageRegistryEnabled); - expect(findPackageAccessLevel().exists()).toBe(false); - }); + const packageRegistryApiForEveryoneEnabledInput = findPackageRegistryApiForEveryoneEnabledInput(); - it('renders the package access level settings with packagesAvailable = true', () => { - wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, - packagesAvailable: true, - }); + if (packageRegistryApiForEveryoneEnabled === 'hidden') { + expect(packageRegistryApiForEveryoneEnabledInput.exists()).toBe(false); + } else if (packageRegistryApiForEveryoneEnabled === 'disabled') { + expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(true); + } else { + expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(false); + await packageRegistryApiForEveryoneEnabledInput.vm.$emit( + 'change', + packageRegistryApiForEveryoneEnabled, + ); + } - expect(findPackageAccessLevel().exists()).toBe(true); - }); + expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); + }, + ); - it('has hidden input field for package registry access level', () => { + it.each` + initialProjectVisibilityLevel | newProjectVisibilityLevel | initialAccessLevel | expectedAccessLevel + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} + `( + 'changes access level when project visibility level changed', + async ({ + initialProjectVisibilityLevel, + newProjectVisibilityLevel, + initialAccessLevel, + expectedAccessLevel, + }) => { wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, packagesAvailable: true, + currentSettings: { + visibilityLevel: initialProjectVisibilityLevel, + packageRegistryAccessLevel: initialAccessLevel, + }, }); - expect(findPackageRegistryAccessLevelHiddenInput().exists()).toBe(true); - }); - - it.each` - projectVisibilityLevel | packageRegistryEnabled | packageRegistryApiForEveryoneEnabled | expectedAccessLevel - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${false} | ${featureAccessLevel.PROJECT_MEMBERS} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${false} | ${featureAccessLevel.EVERYONE} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${'hidden'} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${'hidden'} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - `( - 'sets correct access level', - async ({ - projectVisibilityLevel, - packageRegistryEnabled, - packageRegistryApiForEveryoneEnabled, - expectedAccessLevel, - }) => { - wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, - packagesAvailable: true, - currentSettings: { - visibilityLevel: projectVisibilityLevel, - }, - }); - - await findPackageRegistryEnabledInput().vm.$emit('change', packageRegistryEnabled); - - const packageRegistryApiForEveryoneEnabledInput = findPackageRegistryApiForEveryoneEnabledInput(); - - if (packageRegistryApiForEveryoneEnabled === 'hidden') { - expect(packageRegistryApiForEveryoneEnabledInput.exists()).toBe(false); - } else if (packageRegistryApiForEveryoneEnabled === 'disabled') { - expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(true); - } else { - expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(false); - await packageRegistryApiForEveryoneEnabledInput.vm.$emit( - 'change', - packageRegistryApiForEveryoneEnabled, - ); - } - - expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); - }, - ); + await findProjectVisibilityLevelInput().setValue(newProjectVisibilityLevel); - it.each` - initialProjectVisibilityLevel | newProjectVisibilityLevel | initialAccessLevel | expectedAccessLevel - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} - `( - 'changes access level when project visibility level changed', - async ({ - initialProjectVisibilityLevel, - newProjectVisibilityLevel, - initialAccessLevel, - expectedAccessLevel, - }) => { - wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, - packagesAvailable: true, - currentSettings: { - visibilityLevel: initialProjectVisibilityLevel, - packageRegistryAccessLevel: initialAccessLevel, - }, - }); - - await findProjectVisibilityLevelInput().setValue(newProjectVisibilityLevel); - - expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); - }, - ); - }); + expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); + }, + ); }); describe('Pages', () => { diff --git a/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js b/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js index 35b10375821..11f770fb05e 100644 --- a/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js +++ b/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js @@ -10,7 +10,7 @@ describe('ReportAbuseDropdownItem', () => { let wrapper; const ACTION_PATH = '/abuse_reports/add_category'; - const USER_ID = '1'; + const USER_ID = 1; const REPORTED_FROM_URL = 'http://example.com'; const createComponent = (props) => { diff --git a/spec/frontend/users/profile/components/report_abuse_button_spec.js b/spec/frontend/users/profile/components/report_abuse_button_spec.js index 7ad28566f49..1ef856c9849 100644 --- a/spec/frontend/users/profile/components/report_abuse_button_spec.js +++ b/spec/frontend/users/profile/components/report_abuse_button_spec.js @@ -9,7 +9,7 @@ describe('ReportAbuseButton', () => { let wrapper; const ACTION_PATH = '/abuse_reports/add_category'; - const USER_ID = '1'; + const USER_ID = 1; const REPORTED_FROM_URL = 'http://example.com'; const createComponent = (props) => { diff --git a/spec/frontend/vue_shared/security_reports/store/utils_spec.js b/spec/frontend/vue_shared/security_reports/store/utils_spec.js new file mode 100644 index 00000000000..c8750cd58a0 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/utils_spec.js @@ -0,0 +1,63 @@ +import { enrichVulnerabilityWithFeedback } from '~/vue_shared/security_reports/store/utils'; +import { + FEEDBACK_TYPE_DISMISSAL, + FEEDBACK_TYPE_ISSUE, + FEEDBACK_TYPE_MERGE_REQUEST, +} from '~/vue_shared/security_reports/constants'; + +describe('security reports store utils', () => { + const vulnerability = { uuid: 1 }; + + describe('enrichVulnerabilityWithFeedback', () => { + const dismissalFeedback = { + feedback_type: FEEDBACK_TYPE_DISMISSAL, + finding_uuid: vulnerability.uuid, + }; + const dismissalVuln = { ...vulnerability, isDismissed: true, dismissalFeedback }; + + const issueFeedback = { + feedback_type: FEEDBACK_TYPE_ISSUE, + issue_iid: 1, + finding_uuid: vulnerability.uuid, + }; + const issueVuln = { ...vulnerability, hasIssue: true, issue_feedback: issueFeedback }; + const mrFeedback = { + feedback_type: FEEDBACK_TYPE_MERGE_REQUEST, + merge_request_iid: 1, + finding_uuid: vulnerability.uuid, + }; + const mrVuln = { + ...vulnerability, + hasMergeRequest: true, + merge_request_feedback: mrFeedback, + }; + + it.each` + feedbacks | expected + ${[dismissalFeedback]} | ${dismissalVuln} + ${[{ ...issueFeedback, issue_iid: null }]} | ${vulnerability} + ${[issueFeedback]} | ${issueVuln} + ${[{ ...mrFeedback, merge_request_iid: null }]} | ${vulnerability} + ${[mrFeedback]} | ${mrVuln} + ${[dismissalFeedback, issueFeedback, mrFeedback]} | ${{ ...dismissalVuln, ...issueVuln, ...mrVuln }} + `('returns expected enriched vulnerability: $expected', ({ feedbacks, expected }) => { + const enrichedVulnerability = enrichVulnerabilityWithFeedback(vulnerability, feedbacks); + + expect(enrichedVulnerability).toEqual(expected); + }); + + it('matches correct feedback objects to vulnerability', () => { + const feedbacks = [ + dismissalFeedback, + issueFeedback, + mrFeedback, + { ...dismissalFeedback, finding_uuid: 2 }, + { ...issueFeedback, finding_uuid: 2 }, + { ...mrFeedback, finding_uuid: 2 }, + ]; + const enrichedVulnerability = enrichVulnerabilityWithFeedback(vulnerability, feedbacks); + + expect(enrichedVulnerability).toEqual({ ...dismissalVuln, ...issueVuln, ...mrVuln }); + }); + }); +}); diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 8bf3af44be6..fb50ba89cd3 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -20,6 +20,10 @@ RSpec.describe Ci::BuildMetadata do it_behaves_like 'having unique enum values' + it { is_expected.to belong_to(:build) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:runner_machine) } + describe '#update_timeout_state' do subject { metadata } diff --git a/spec/models/ci/runner_machine_spec.rb b/spec/models/ci/runner_machine_spec.rb index e39f987110f..9fc35006233 100644 --- a/spec/models/ci/runner_machine_spec.rb +++ b/spec/models/ci/runner_machine_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model it_behaves_like 'having unique enum values' it { is_expected.to belong_to(:runner) } + it { is_expected.to have_many(:build_metadata) } describe 'validation' do it { is_expected.to validate_presence_of(:runner) } diff --git a/spec/policies/packages/policies/project_policy_spec.rb b/spec/policies/packages/policies/project_policy_spec.rb index 5d54ee54572..5c267ff5ac5 100644 --- a/spec/policies/packages/policies/project_policy_spec.rb +++ b/spec/policies/packages/policies/project_policy_spec.rb @@ -122,39 +122,6 @@ RSpec.describe Packages::Policies::ProjectPolicy do end end - context 'with feature flag disabled' do - before do - stub_feature_flags(package_registry_access_level: false) - end - - where(:project, :current_user, :expect_to_be_allowed) do - ref(:private_project) | ref(:anonymous) | false - ref(:private_project) | ref(:non_member) | false - ref(:private_project) | ref(:guest) | false - ref(:internal_project) | ref(:anonymous) | false - ref(:public_project) | ref(:admin) | true - ref(:public_project) | ref(:owner) | true - ref(:public_project) | ref(:maintainer) | true - ref(:public_project) | ref(:developer) | true - ref(:public_project) | ref(:reporter) | true - ref(:public_project) | ref(:guest) | true - ref(:public_project) | ref(:non_member) | true - ref(:public_project) | ref(:anonymous) | true - end - - with_them do - it do - project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) - - if expect_to_be_allowed - is_expected.to be_allowed(:read_package) - else - is_expected.to be_disallowed(:read_package) - end - end - end - end - context 'with admin' do let(:current_user) { admin } diff --git a/spec/requests/abuse_reports_controller_spec.rb b/spec/requests/abuse_reports_controller_spec.rb index 49a80689c65..934f123e45b 100644 --- a/spec/requests/abuse_reports_controller_spec.rb +++ b/spec/requests/abuse_reports_controller_spec.rb @@ -5,9 +5,12 @@ require 'spec_helper' RSpec.describe AbuseReportsController, feature_category: :insider_threat do let(:reporter) { create(:user) } let(:user) { create(:user) } + let(:abuse_category) { 'spam' } + let(:attrs) do attributes_for(:abuse_report) do |hash| hash[:user_id] = user.id + hash[:category] = abuse_category end end @@ -55,8 +58,6 @@ RSpec.describe AbuseReportsController, feature_category: :insider_threat do describe 'POST add_category', :aggregate_failures do subject(:request) { post add_category_abuse_reports_path, params: request_params } - let(:abuse_category) { 'spam' } - context 'when user is reported for abuse' do let(:ref_url) { 'http://example.com' } let(:request_params) do @@ -80,6 +81,17 @@ RSpec.describe AbuseReportsController, feature_category: :insider_threat do reported_from_url: ref_url ) end + + it 'tracks the snowplow event' do + subject + + expect_snowplow_event( + category: 'ReportAbuse', + action: 'select_abuse_category', + property: abuse_category, + user: user + ) + end end context 'when abuse_report is missing in params' do @@ -149,15 +161,35 @@ RSpec.describe AbuseReportsController, feature_category: :insider_threat do expect(response).to redirect_to root_path end + + it 'tracks the snowplow event' do + post abuse_reports_path(abuse_report: attrs) + + expect_snowplow_event( + category: 'ReportAbuse', + action: 'submit_form', + property: abuse_category, + user: user + ) + end end context 'with invalid attributes' do - it 'redirects back to root' do + before do attrs.delete(:user_id) + end + + it 'redirects back to root' do post abuse_reports_path(abuse_report: attrs) expect(response).to redirect_to root_path end + + it 'does not track the snowplow event' do + post abuse_reports_path(abuse_report: attrs) + + expect_no_snowplow_event + end end end end diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index dc631ad7921..be76e55269a 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -227,7 +227,7 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme context 'an agent is found' do let_it_be(:agent_token) { create(:cluster_agent_token) } - shared_examples 'agent token tracking' + include_examples 'agent token tracking' context 'project is public' do let(:project) { create(:project, :public) } diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb index 9d859403465..e9056edbafa 100644 --- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb +++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb @@ -20,10 +20,9 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - expect(dropdown).to have_link('Report abuse to administrator', href: abuse_report_path) - if type == 'issue' || type == 'merge_request' expect(dropdown).to have_button('Delete comment') + expect(dropdown).to have_button('Report abuse to administrator') else expect(dropdown).to have_link('Delete comment', href: note_url(note, project)) end @@ -33,10 +32,21 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - dropdown.click_link('Report abuse to administrator') + if type == 'issue' || type == 'merge_request' + dropdown.click_button('Report abuse to administrator') + + choose "They're posting spam." + click_button "Next" - expect(find('#user_name')['value']).to match(note.author.username) - expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + expect(find('#user_name')['value']).to match(note.author.username) + expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + expect(find('#abuse_report_category', visible: false)['value']).to match('spam') + else + dropdown.click_link('Report abuse to administrator') + + expect(find('#user_name')['value']).to match(note.author.username) + expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + end end def open_dropdown(dropdown) |