diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 15:08:18 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 15:08:18 +0300 |
commit | d00cd98a2b1b3f0899677f61257821c94cde8e31 (patch) | |
tree | 602dc6d5ca71906ff282d87808c0c1d04b853aeb /app | |
parent | edf0e5b64384499283b406f9087e890ac4fad13f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
11 files changed, 396 insertions, 125 deletions
diff --git a/app/assets/javascripts/boards/graphql/cache_updates.js b/app/assets/javascripts/boards/graphql/cache_updates.js index 084809e4e60..1bd3ffea3df 100644 --- a/app/assets/javascripts/boards/graphql/cache_updates.js +++ b/app/assets/javascripts/boards/graphql/cache_updates.js @@ -2,10 +2,21 @@ import produce from 'immer'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import { listsDeferredQuery } from 'ee_else_ce/boards/constants'; -export function removeItemFromList({ query, variables, boardType, id, issuableType, cache }) { +export function removeItemFromList({ + query, + variables, + boardType, + id, + issuableType, + listId = undefined, + cache, +}) { cache.updateQuery({ query, variables }, (sourceData) => produce(sourceData, (draftData) => { - const { nodes: items } = draftData[boardType].board.lists.nodes[0][`${issuableType}s`]; + const list = listId + ? draftData[boardType]?.board.lists.nodes.find((l) => l.id === listId) + : draftData[boardType].board.lists.nodes[0]; + const { nodes: items } = list[`${issuableType}s`]; items.splice( items.findIndex((item) => item.id === id), 1, @@ -21,11 +32,15 @@ export function addItemToList({ issuable, newIndex, issuableType, + listId = undefined, cache, }) { cache.updateQuery({ query, variables }, (sourceData) => produce(sourceData, (draftData) => { - const { nodes: items } = draftData[boardType].board.lists.nodes[0][`${issuableType}s`]; + const list = listId + ? draftData[boardType]?.board.lists.nodes.find((l) => l.id === listId) + : draftData[boardType].board.lists.nodes[0]; + const { nodes: items } = list[`${issuableType}s`]; items.splice(newIndex, 0, issuable); }), ); diff --git a/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_expired.vue b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_expired.vue new file mode 100644 index 00000000000..7e0c81ce8c2 --- /dev/null +++ b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_expired.vue @@ -0,0 +1,54 @@ +<script> +import { GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import ResourceParentLink from '../resource_parent_link.vue'; +import ContributionEventBase from './contribution_event_base.vue'; + +export default { + name: 'ContributionEventExpired', + i18n: { + message: s__( + 'ContributionEvent|Removed due to membership expiration from %{resourceParentLink}.', + ), + }, + components: { ContributionEventBase, ResourceParentLink, GlSprintf }, + props: { + /** + * Expected format + * { + * created_at: string; + * action: "expired" + * author: { + * id: number; + * username: string; + * name: string; + * state: string; + * avatar_url: string; + * web_url: string; + * }; + * resource_parent: { + * type: "project"; + * full_name: string; + * full_path: string; + * web_url: string; + * avatar_url: string; + * }; + * }; + */ + event: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <contribution-event-base :event="event" icon-name="expire"> + <gl-sprintf :message="$options.i18n.message"> + <template #resourceParentLink> + <resource-parent-link :event="event" /> + </template> + </gl-sprintf> + </contribution-event-base> +</template> diff --git a/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_joined.vue b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_joined.vue new file mode 100644 index 00000000000..9ecf0f88772 --- /dev/null +++ b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_joined.vue @@ -0,0 +1,52 @@ +<script> +import { GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import ResourceParentLink from '../resource_parent_link.vue'; +import ContributionEventBase from './contribution_event_base.vue'; + +export default { + name: 'ContributionEventJoined', + i18n: { + message: s__('ContributionEvent|Joined project %{resourceParentLink}.'), + }, + components: { ContributionEventBase, ResourceParentLink, GlSprintf }, + props: { + /** + * Expected format + * { + * created_at: string; + * action: "joined" + * author: { + * id: number; + * username: string; + * name: string; + * state: string; + * avatar_url: string; + * web_url: string; + * }; + * resource_parent: { + * type: "project"; + * full_name: string; + * full_path: string; + * web_url: string; + * avatar_url: string; + * }; + * }; + */ + event: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <contribution-event-base :event="event" icon-name="users"> + <gl-sprintf :message="$options.i18n.message"> + <template #resourceParentLink> + <resource-parent-link :event="event" /> + </template> + </gl-sprintf> + </contribution-event-base> +</template> diff --git a/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_left.vue b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_left.vue new file mode 100644 index 00000000000..2fd9383c05e --- /dev/null +++ b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_left.vue @@ -0,0 +1,52 @@ +<script> +import { GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import ResourceParentLink from '../resource_parent_link.vue'; +import ContributionEventBase from './contribution_event_base.vue'; + +export default { + name: 'ContributionEventLeft', + i18n: { + message: s__('ContributionEvent|Left project %{resourceParentLink}.'), + }, + components: { ContributionEventBase, ResourceParentLink, GlSprintf }, + props: { + /** + * Expected format + * { + * created_at: string; + * action: "left" + * author: { + * id: number; + * username: string; + * name: string; + * state: string; + * avatar_url: string; + * web_url: string; + * }; + * resource_parent: { + * type: "project"; + * full_name: string; + * full_path: string; + * web_url: string; + * avatar_url: string; + * }; + * }; + */ + event: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <contribution-event-base :event="event" icon-name="leave"> + <gl-sprintf :message="$options.i18n.message"> + <template #resourceParentLink> + <resource-parent-link :event="event" /> + </template> + </gl-sprintf> + </contribution-event-base> +</template> diff --git a/app/assets/javascripts/contribution_events/components/contribution_events.vue b/app/assets/javascripts/contribution_events/components/contribution_events.vue index 41ec4f5692e..c704c9bd048 100644 --- a/app/assets/javascripts/contribution_events/components/contribution_events.vue +++ b/app/assets/javascripts/contribution_events/components/contribution_events.vue @@ -1,7 +1,15 @@ <script> import EmptyComponent from '~/vue_shared/components/empty_component'; -import { EVENT_TYPE_APPROVED } from '../constants'; +import { + EVENT_TYPE_APPROVED, + EVENT_TYPE_EXPIRED, + EVENT_TYPE_JOINED, + EVENT_TYPE_LEFT, +} from '../constants'; import ContributionEventApproved from './contribution_event/contribution_event_approved.vue'; +import ContributionEventExpired from './contribution_event/contribution_event_expired.vue'; +import ContributionEventJoined from './contribution_event/contribution_event_joined.vue'; +import ContributionEventLeft from './contribution_event/contribution_event_left.vue'; export default { props: { @@ -99,6 +107,15 @@ export default { case EVENT_TYPE_APPROVED: return ContributionEventApproved; + case EVENT_TYPE_EXPIRED: + return ContributionEventExpired; + + case EVENT_TYPE_JOINED: + return ContributionEventJoined; + + case EVENT_TYPE_LEFT: + return ContributionEventLeft; + default: return EmptyComponent; } diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue index 3157653648b..3d29a629dd9 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue @@ -10,9 +10,13 @@ import { GlLoadingIcon, GlModal, GlSprintf, + GlKeysetPagination, } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert'; +import { NEXT, PREV } from '~/vue_shared/components/pagination/constants'; import { numberToHumanSize } from '~/lib/utils/number_utils'; +import { scrollToElement } from '~/lib/utils/common_utils'; import { __, s__ } from '~/locale'; import FileSha from '~/packages_and_registries/package_registry/components/details/file_sha.vue'; import Tracking from '~/tracking'; @@ -53,6 +57,7 @@ export default { GlButton, GlLoadingIcon, GlModal, + GlKeysetPagination, GlSprintf, FileIcon, TimeAgoTooltip, @@ -94,10 +99,17 @@ export default { return this.queryVariables; }, update(data) { - return data.package?.packageFiles ?? {}; + return data.package?.packageFiles?.nodes ?? []; }, - error() { + result({ data }) { + const { packageFiles } = data?.package ?? {}; + if (packageFiles?.pageInfo) { + this.pageInfo = packageFiles.pageInfo; + } + }, + error(error) { this.fetchPackageFilesError = true; + Sentry.captureException(error); }, }, }, @@ -105,23 +117,21 @@ export default { return { fetchPackageFilesError: false, filesToDelete: [], - packageFiles: {}, + packageFiles: [], mutationLoading: false, selectedReferences: [], + pageInfo: {}, }; }, computed: { - files() { - return this.packageFiles?.nodes ?? []; - }, areFilesSelected() { return this.selectedReferences.length > 0; }, areAllFilesSelected() { - return this.files.length > 0 && this.files.every(this.isSelected); + return this.packageFiles.length > 0 && this.packageFiles.every(this.isSelected); }, filesTableRows() { - return this.files.map((pf) => ({ + return this.packageFiles.map((pf) => ({ ...pf, size: this.formatSize(pf.size), })); @@ -168,6 +178,10 @@ export default { first: GRAPHQL_PACKAGE_FILES_PAGE_SIZE, }; }, + showPagination() { + const { hasPreviousPage, hasNextPage } = this.pageInfo; + return hasPreviousPage || hasNextPage; + }, tracking() { return { category: packageTypeToTrackCategory(this.packageType), @@ -258,7 +272,7 @@ export default { }, handleFileDelete(files) { this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION); - if (files.length === this.files.length && !this.packageFiles?.pageInfo?.hasNextPage) { + if (files.length === this.packageFiles.length && !this.pageInfo.hasNextPage) { this.$emit( 'delete-all-files', this.hasOneItem(files) @@ -281,6 +295,41 @@ export default { } this.deletePackageFiles(this.filesToDelete.map((file) => file.id)); }, + fetchPreviousFilesPage() { + return this.$apollo.queries.packageFiles + .fetchMore({ + variables: { + first: null, + last: GRAPHQL_PACKAGE_FILES_PAGE_SIZE, + before: this.pageInfo.startCursor, + }, + }) + .then(() => { + this.scrollAndFocus(); + }); + }, + fetchNextFilesPage() { + return this.$apollo.queries.packageFiles + .fetchMore({ + variables: { + first: GRAPHQL_PACKAGE_FILES_PAGE_SIZE, + last: null, + after: this.pageInfo.endCursor, + }, + }) + .then(() => { + this.scrollAndFocus(); + }); + }, + scrollAndFocus() { + scrollToElement(this.$el); + + // get first focusable row + const focusable = this.$el.querySelector('tbody tr'); + if (focusable) { + focusable.focus(); + } + }, }, i18n: { deleteFile: s__('PackageRegistry|Delete asset'), @@ -295,6 +344,8 @@ export default { deleteSelected: s__('PackageRegistry|Delete selected'), moreActionsText: __('More actions'), fetchPackageFilesErrorMessage: FETCH_PACKAGE_FILES_ERROR_MESSAGE, + prev: PREV, + next: NEXT, }, modal: { fileDeletePrimaryAction: { @@ -334,102 +385,116 @@ export default { > {{ $options.i18n.fetchPackageFilesErrorMessage }} </gl-alert> - <gl-table - v-else - :busy="isLoading" - :fields="filesTableHeaderFields" - :items="filesTableRows" - show-empty - selectable - select-mode="multi" - selected-variant="primary" - :tbody-tr-attr="{ 'data-testid': 'file-row' }" - @row-selected="updateSelectedReferences" - > - <template #table-busy> - <gl-loading-icon size="lg" class="gl-my-5" /> - </template> - <template #head(checkbox)="{ selectAllRows, clearSelected }"> - <gl-form-checkbox - v-if="canDelete" - data-testid="package-files-checkbox-all" - :checked="areAllFilesSelected" - :indeterminate="hasSelectedSomeFiles" - @change="areAllFilesSelected ? clearSelected() : selectAllRows()" - /> - </template> + <template v-else> + <gl-table + ref="table" + :busy="isLoading" + :fields="filesTableHeaderFields" + :items="filesTableRows" + show-empty + selectable + select-mode="multi" + selected-variant="primary" + :tbody-tr-attr="{ 'data-testid': 'file-row' }" + @row-selected="updateSelectedReferences" + > + <template #table-busy> + <gl-loading-icon size="lg" class="gl-my-5" /> + </template> + <template #head(checkbox)="{ selectAllRows, clearSelected }"> + <gl-form-checkbox + v-if="canDelete" + data-testid="package-files-checkbox-all" + :checked="areAllFilesSelected" + :indeterminate="hasSelectedSomeFiles" + @change="areAllFilesSelected ? clearSelected() : selectAllRows()" + /> + </template> - <template #cell(checkbox)="{ rowSelected, selectRow, unselectRow }"> - <gl-form-checkbox - v-if="canDelete" - class="gl-mt-1" - :checked="rowSelected" - data-testid="package-files-checkbox" - @change="rowSelected ? unselectRow() : selectRow()" - /> - </template> + <template #cell(checkbox)="{ rowSelected, selectRow, unselectRow }"> + <gl-form-checkbox + v-if="canDelete" + class="gl-mt-1" + :checked="rowSelected" + data-testid="package-files-checkbox" + @change="rowSelected ? unselectRow() : selectRow()" + /> + </template> - <template #cell(name)="{ item, toggleDetails, detailsShowing }"> - <gl-button - v-if="hasDetails(item)" - :icon="detailsShowing ? 'chevron-up' : 'chevron-down'" - :aria-label="detailsShowing ? __('Collapse') : __('Expand')" - category="tertiary" - size="small" - @click=" - toggleDetails(); - trackToggleDetails(detailsShowing); - " - /> - <gl-link - :href="item.downloadPath" - class="gl-text-gray-500" - data-testid="download-link" - @click="track($options.trackingActions.DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION)" - > - <file-icon - :file-name="item.fileName" - css-classes="gl-relative file-icon" - class="gl-mr-1 gl-relative" + <template #cell(name)="{ item, toggleDetails, detailsShowing }"> + <gl-button + v-if="hasDetails(item)" + :icon="detailsShowing ? 'chevron-up' : 'chevron-down'" + :aria-label="detailsShowing ? __('Collapse') : __('Expand')" + category="tertiary" + size="small" + @click=" + toggleDetails(); + trackToggleDetails(detailsShowing); + " /> - <span>{{ item.fileName }}</span> - </gl-link> - </template> + <gl-link + :href="item.downloadPath" + class="gl-text-gray-500" + data-testid="download-link" + @click="track($options.trackingActions.DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION)" + > + <file-icon + :file-name="item.fileName" + css-classes="gl-relative file-icon" + class="gl-mr-1 gl-relative" + /> + <span>{{ item.fileName }}</span> + </gl-link> + </template> - <template #cell(created)="{ item }"> - <time-ago-tooltip :time="item.createdAt" /> - </template> + <template #cell(created)="{ item }"> + <time-ago-tooltip :time="item.createdAt" /> + </template> - <template #cell(actions)="{ item }"> - <gl-dropdown - category="tertiary" - icon="ellipsis_v" - :text-sr-only="true" - :text="$options.i18n.moreActionsText" - no-caret - right - > - <gl-dropdown-item data-testid="delete-file" @click="handleFileDelete([item])"> - {{ $options.i18n.deleteFile }} - </gl-dropdown-item> - </gl-dropdown> - </template> + <template #cell(actions)="{ item }"> + <gl-dropdown + category="tertiary" + icon="ellipsis_v" + :text-sr-only="true" + :text="$options.i18n.moreActionsText" + no-caret + right + > + <gl-dropdown-item data-testid="delete-file" @click="handleFileDelete([item])"> + {{ $options.i18n.deleteFile }} + </gl-dropdown-item> + </gl-dropdown> + </template> - <template #row-details="{ item }"> - <div - class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100" - > - <file-sha - v-if="item.fileSha256" - data-testid="sha-256" - title="SHA-256" - :sha="item.fileSha256" - /> - <file-sha v-if="item.fileMd5" data-testid="md5" title="MD5" :sha="item.fileMd5" /> - <file-sha v-if="item.fileSha1" data-testid="sha-1" title="SHA-1" :sha="item.fileSha1" /> - </div> - </template> - </gl-table> + <template #row-details="{ item }"> + <div + class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100" + > + <file-sha + v-if="item.fileSha256" + data-testid="sha-256" + title="SHA-256" + :sha="item.fileSha256" + /> + <file-sha v-if="item.fileMd5" data-testid="md5" title="MD5" :sha="item.fileMd5" /> + <file-sha v-if="item.fileSha1" data-testid="sha-1" title="SHA-1" :sha="item.fileSha1" /> + </div> + </template> + </gl-table> + <div class="gl-display-flex gl-justify-content-center"> + <gl-keyset-pagination + v-if="showPagination" + :disabled="isLoading" + v-bind="pageInfo" + :prev-text="$options.i18n.prev" + :next-text="$options.i18n.next" + class="gl-mt-3" + @prev="fetchPreviousFilesPage" + @next="fetchNextFilesPage" + /> + </div> + </template> <gl-modal ref="deleteFilesModal" 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 80712c2991c..364bd430f07 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -235,4 +235,4 @@ export const REQUEST_FORWARDING_HELP_PAGE_PATH = helpPagePath( ); export const GRAPHQL_PACKAGE_PIPELINES_PAGE_SIZE = 10; -export const GRAPHQL_PACKAGE_FILES_PAGE_SIZE = 100; +export const GRAPHQL_PACKAGE_FILES_PAGE_SIZE = 20; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js index d05ff5daad4..d630e040d52 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js @@ -22,6 +22,7 @@ export const apolloProvider = new VueApollo({ merge: mergeVariables, }, packageFiles: { + keyArgs: ['id'], merge: mergeVariables, }, }, diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql index e6f292ec1d3..7a389b2aa5c 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql @@ -1,9 +1,17 @@ -query getPackageFiles($id: PackagesPackageID!, $first: Int) { +#import "~/graphql_shared/fragments/page_info.fragment.graphql" + +query getPackageFiles( + $id: PackagesPackageID! + $first: Int + $last: Int + $after: String + $before: String +) { package(id: $id) { id - packageFiles(first: $first) { + packageFiles(after: $after, before: $before, first: $first, last: $last) { pageInfo { - hasNextPage + ...PageInfo } nodes { id diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue index 5baeb309f79..8bf4d8816be 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue @@ -1,10 +1,9 @@ <script> -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdown } from '@gitlab/ui'; export default { components: { - GlDropdown, - GlDropdownItem, + GlDisclosureDropdown, }, props: { commits: { @@ -13,28 +12,36 @@ export default { default: () => [], }, }, + computed: { + dropdownItems() { + return this.commits.map((commit) => ({ + text: commit.title, + extraAttrs: { + text: commit.shortId || commit.short_Id, + }, + action: () => { + this.$emit('input', commit.message); + }, + })); + }, + }, }; </script> <template> <div> - <gl-dropdown - right - text="Use an existing commit message" + <gl-disclosure-dropdown + placement="right" + toggle-text="Use an existing commit message" category="tertiary" - variant="confirm" + :items="dropdownItems" size="small" class="mr-commit-dropdown" > - <gl-dropdown-item - v-for="(commit, index) in commits" - :key="index" - class="text-nowrap text-truncate" - @click="$emit('input', commit.message)" - > - <span class="monospace mr-2">{{ commit.shortId || commit.short_id }}</span> - {{ commit.title }} - </gl-dropdown-item> - </gl-dropdown> + <template #list-item="{ item }"> + <span class="gl-mr-2">{{ item.extraAttrs.text }}</span> + {{ item.text }} + </template> + </gl-disclosure-dropdown> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_child_nodes.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_child_nodes.js index a79e88a1132..b972d8ece91 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_child_nodes.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_child_nodes.js @@ -22,7 +22,7 @@ const format = (node, scope = '') => { .split(newlineRegex) .map((newline) => generateHLJSTag(scope, newline, true)) .join('\n'); - } else if (node.scope || node.sublanguage) { + } else if (node.children) { const { children } = node; if (children.length && children.length === 1) { buffer += format(children[0], node.scope); |