diff options
Diffstat (limited to 'app/assets/javascripts')
35 files changed, 256 insertions, 225 deletions
diff --git a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js index 070ce38c8aa..d97f11a0acd 100644 --- a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js +++ b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js @@ -72,22 +72,20 @@ export const fetchDrafts = ({ commit, getters, state, dispatch }) => }), ); -export const publishSingleDraft = ({ commit, dispatch, getters }, draftId) => { +export const publishSingleDraft = ({ commit, getters }, draftId) => { commit(types.REQUEST_PUBLISH_DRAFT, draftId); service .publishDraft(getters.getNotesData.draftsPublishPath, draftId) - .then(() => dispatch('updateDiscussionsAfterPublish')) .then(() => commit(types.RECEIVE_PUBLISH_DRAFT_SUCCESS, draftId)) .catch(() => commit(types.RECEIVE_PUBLISH_DRAFT_ERROR, draftId)); }; -export const publishReview = ({ commit, dispatch, getters }, noteData = {}) => { +export const publishReview = ({ commit, getters }, noteData = {}) => { commit(types.REQUEST_PUBLISH_REVIEW); return service .publish(getters.getNotesData.draftsPublishPath, noteData) - .then(() => dispatch('updateDiscussionsAfterPublish')) .then(() => commit(types.RECEIVE_PUBLISH_REVIEW_SUCCESS)) .catch((e) => { commit(types.RECEIVE_PUBLISH_REVIEW_ERROR); @@ -96,18 +94,6 @@ export const publishReview = ({ commit, dispatch, getters }, noteData = {}) => { }); }; -export const updateDiscussionsAfterPublish = async ({ dispatch, getters, rootGetters }) => { - await dispatch( - 'fetchDiscussions', - { path: getters.getNotesData.discussionsPath }, - { root: true }, - ); - - dispatch('diffs/assignDiscussionsToDiff', rootGetters.discussionsStructuredByLineCode, { - root: true, - }); -}; - export const updateDraft = ( { commit, getters }, { note, noteText, resolveDiscussion, position, flashContainer, callback, errorCallback }, diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue index fce7aabf0cf..3da2f27c1b9 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue @@ -248,7 +248,6 @@ export default { <template #downstream> <linked-pipelines-column v-if="showDownstreamPipelines" - class="gl-mr-5" :class="{ 'gl-sm-ml-3': isNewPipelineGraph }" :config-paths="configPaths" :linked-pipelines="downstreamPipelines" diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue index 9bd0ec6d793..0d72373a0f5 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue @@ -11,7 +11,10 @@ export default { }; </script> <template> - <div class="gl-display-flex" :class="{ 'gl-flex-wrap gl-w-full': isNewPipelineGraph }"> + <div + class="gl-display-flex" + :class="{ 'gl-flex-wrap gl-sm-flex-nowrap gl-w-full': isNewPipelineGraph }" + > <slot name="upstream"></slot> <slot name="main"></slot> <slot name="downstream"></slot> diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue index 67918ea8d1a..c715d6af28a 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue @@ -74,7 +74,7 @@ export default { left: 'gl-mx-6', }; const positionValues = { - right: 'gl-ml-5', + right: 'gl-mx-5', left: 'gl-mx-4 gl-flex-basis-full', }; const usePositionValues = this.isNewPipelineGraph ? positionValues : positionValuesOld; diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue index e144b9aab0c..01a9c6d030d 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue @@ -179,7 +179,7 @@ export default { <template #stages> <div data-testid="stage-column-title" - class="gl-display-flex gl-justify-content-space-between gl-relative" + class="stage-column-title gl-display-flex gl-justify-content-space-between gl-relative" :class="titleClasses" > <span :title="name" class="gl-text-truncate gl-pr-3 gl-w-85p"> diff --git a/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue b/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue index 5c1841615ab..dc4a2d91c84 100644 --- a/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue +++ b/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue @@ -403,12 +403,7 @@ export default { {{ commitTitle }} </h3> <div> - <ci-icon - :status="detailedStatus" - show-status-text - :show-link="false" - class="gl-display-inline-block gl-mb-3" - /> + <ci-icon :status="detailedStatus" show-status-text :show-link="false" class="gl-mb-3" /> <div class="gl-ml-2 gl-mb-3 gl-display-inline-block gl-h-6"> <gl-link v-if="user" diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 7c3d6dc8c42..9971d3bf7f8 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -366,22 +366,11 @@ export default { handleLocationHash(); this.autoScrolled = true; }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS); - this.unwatchDiscussions = this.$watch( - () => `${this.flatBlobsList.length}:${this.$store.state.notes.discussions.length}`, - () => { - this.setDiscussions(); - - if (this.$store.state.notes.doneFetchingBatchDiscussions) { - this.unwatchDiscussions(); - } - }, - ); - - this.unwatchRetrievingBatches = this.$watch( - () => `${this.retrievingBatches}:${this.$store.state.notes.discussions.length}`, - () => { - if (!this.retrievingBatches && this.$store.state.notes.discussions.length) { - this.unwatchRetrievingBatches(); + this.$watch( + () => this.$store.state.notes.discussions.length, + (newVal, prevVal) => { + if (newVal > prevVal) { + this.setDiscussions(); } }, ); diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue index 8915f32eadf..556f72059c2 100644 --- a/app/assets/javascripts/diffs/components/diff_discussions.vue +++ b/app/assets/javascripts/diffs/components/diff_discussions.vue @@ -39,12 +39,6 @@ export default { }, methods: { ...mapActions(['toggleDiscussion']), - ...mapActions('diffs', ['removeDiscussionsFromDiff']), - deleteNoteHandler(discussion) { - if (discussion.notes.length <= 1) { - this.removeDiscussionsFromDiff(discussion); - } - }, isExpanded(discussion) { return this.shouldCollapseDiscussions ? discussion.expanded : true; }, @@ -90,7 +84,6 @@ export default { :line="line" :help-page-path="helpPagePath" :should-scroll-to-note="false" - @noteDeleted="deleteNoteHandler" > <template v-if="renderAvatarBadge" #avatar-badge> <design-note-pin diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index d86a88f97b8..756f76569dc 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -419,7 +419,11 @@ export const assignDiscussionsToDiff = ( }; export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => { - const { file_hash: fileHash, line_code: lineCode, id } = removeDiscussion; + const { + diff_file: { file_hash: fileHash }, + line_code: lineCode, + id, + } = removeDiscussion; commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode, id }); }; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 31369b169f5..a9a2c35faa4 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -198,9 +198,10 @@ export default { return { ...line, discussionsExpanded: - line.discussions && line.discussions.length + line.discussionsExpanded || + (line.discussions && line.discussions.length ? line.discussions.some((disc) => !disc.resolved) || isLineNoteTargeted - : false, + : false), }; }; diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 15d2ab71bc8..fb467a606b9 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -338,7 +338,7 @@ function prepareLine(line, file) { problems.brokenSymlink || problems.fileOnlyMoved || problems.brokenLineCode, ), rich_text: cleanRichText(line.rich_text), - discussionsExpanded: true, + discussionsExpanded: false, discussions: [], hasForm: false, text: undefined, diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index 795cbf5327a..fd5fcb12cc5 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -112,6 +112,9 @@ export default { canSetupReviewApp() { return this.environmentApp?.reviewApp?.canSetupReviewApp; }, + hasReviewApp() { + return this.environmentApp?.reviewApp?.hasReviewApp; + }, canCleanUpEnvs() { return this.environmentApp?.canStopStaleEnvironments; }, @@ -157,7 +160,10 @@ export default { }; }, openReviewAppModal() { - if (!this.canSetupReviewApp) { + // we don't show the Enable review apps button + // if a user cannot setup a review app or review + // apps are already configured + if (!this.canSetupReviewApp || this.hasReviewApp) { return null; } diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue index 2ee7b604253..126a3a84d66 100644 --- a/app/assets/javascripts/issuable/components/related_issuable_item.vue +++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue @@ -194,7 +194,7 @@ export default { <div class="item-attributes-area gl-display-flex gl-align-items-center gl-flex-wrap gl-gap-3" > - <span v-if="hasPipeline" class="mr-ci-status order-md-last"> + <span v-if="hasPipeline" class="mr-ci-status order-md-last gl-md-ml-3 gl-mr-n2"> <a :href="pipelinePath"> <ci-icon :status="pipelineStatus" :title="pipelineStatusTooltip" /> </a> @@ -203,7 +203,7 @@ export default { <issue-milestone v-if="hasMilestone" :milestone="milestone" - class="item-milestone gl-font-sm gl-display-flex gl-align-items-center order-md-first" + class="item-milestone gl-font-sm gl-display-flex gl-align-items-center order-md-first gl-ml-2" /> <!-- Flex order for slots is defined in the parent component: e.g. related_issues_block.vue --> diff --git a/app/assets/javascripts/issuable/components/status_badge.vue b/app/assets/javascripts/issuable/components/status_badge.vue index 949fb3c1ce5..35f6446d582 100644 --- a/app/assets/javascripts/issuable/components/status_badge.vue +++ b/app/assets/javascripts/issuable/components/status_badge.vue @@ -14,29 +14,29 @@ import { const badgePropertiesMap = { [TYPE_EPIC]: { [STATUS_OPEN]: { - icon: 'epic', + icon: 'issue-open-m', text: __('Open'), variant: 'success', }, [STATUS_CLOSED]: { - icon: 'epic-closed', + icon: 'issue-close', text: __('Closed'), variant: 'info', }, }, [TYPE_ISSUE]: { [STATUS_OPEN]: { - icon: 'issues', + icon: 'issue-open-m', text: __('Open'), variant: 'success', }, [STATUS_CLOSED]: { - icon: 'issue-closed', + icon: 'issue-close', text: __('Closed'), variant: 'info', }, [STATUS_LOCKED]: { - icon: 'issues', + icon: 'issue-open-m', text: __('Open'), variant: 'success', }, diff --git a/app/assets/javascripts/issues/show/components/issue_header.vue b/app/assets/javascripts/issues/show/components/issue_header.vue index c205a6361c7..96eb8fbb3c7 100644 --- a/app/assets/javascripts/issues/show/components/issue_header.vue +++ b/app/assets/javascripts/issues/show/components/issue_header.vue @@ -82,7 +82,7 @@ export default { return this.issuableState === STATUS_OPEN || this.issuableState === STATUS_REOPENED; }, statusIcon() { - return this.isOpen ? 'issues' : 'issue-closed'; + return this.isOpen ? 'issue-open-m' : 'issue-close'; }, statusText() { if (this.isOpen) { diff --git a/app/assets/javascripts/issues/show/components/sticky_header.vue b/app/assets/javascripts/issues/show/components/sticky_header.vue index d75f6c75ba5..18e37c4216c 100644 --- a/app/assets/javascripts/issues/show/components/sticky_header.vue +++ b/app/assets/javascripts/issues/show/components/sticky_header.vue @@ -2,12 +2,7 @@ import { GlBadge, GlIcon, GlIntersectionObserver, GlLink } from '@gitlab/ui'; import HiddenBadge from '~/issuable/components/hidden_badge.vue'; import LockedBadge from '~/issuable/components/locked_badge.vue'; -import { - issuableStatusText, - STATUS_CLOSED, - TYPE_EPIC, - WORKSPACE_PROJECT, -} from '~/issues/constants'; +import { issuableStatusText, STATUS_CLOSED, WORKSPACE_PROJECT } from '~/issues/constants'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; export default { @@ -60,10 +55,7 @@ export default { return this.issuableStatus === STATUS_CLOSED; }, statusIcon() { - if (this.issuableType === TYPE_EPIC) { - return this.isClosed ? 'epic-closed' : 'epic'; - } - return this.isClosed ? 'issue-closed' : 'issues'; + return this.isClosed ? 'issue-close' : 'issue-open-m'; }, statusText() { return issuableStatusText[this.issuableStatus]; diff --git a/app/assets/javascripts/lib/utils/color_utils.js b/app/assets/javascripts/lib/utils/color_utils.js index a9f4257e28b..74c9f7de8c1 100644 --- a/app/assets/javascripts/lib/utils/color_utils.js +++ b/app/assets/javascripts/lib/utils/color_utils.js @@ -46,5 +46,5 @@ export function darkModeEnabled() { if (isWebIde) { return ideDarkThemes.includes(window.gon?.user_color_scheme); } - return document.body.classList.contains('gl-dark'); + return document.documentElement.classList.contains('gl-dark'); } diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index e0b1f7a8c6a..493beb8cea9 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -290,9 +290,6 @@ export default { parent: this.$el, }); }, - deleteNoteHandler(note) { - this.$emit('noteDeleted', this.discussion, note); - }, onStartReplying(discussionId) { if (this.discussion.id === discussionId) { this.showReplyForm(); @@ -329,7 +326,6 @@ export default { :is-overview-tab="isOverviewTab" :should-scroll-to-note="shouldScrollToNote" @startReplying="showReplyForm" - @deleteNote="deleteNoteHandler" > <template #avatar-badge> <slot name="avatar-badge"></slot> diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 966f4184780..a995b9fa214 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -318,11 +318,6 @@ export default { const note = noteData; const selectedDiscussion = state.discussions.find((disc) => disc.id === note.id); note.expanded = true; // override expand flag to prevent collapse - if (note.diff_file) { - Object.assign(note, { - file_hash: note.diff_file.file_hash, - }); - } Object.assign(selectedDiscussion, { ...note }); }, diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js index da8837c21da..54ca8311621 100644 --- a/app/assets/javascripts/observability/client.js +++ b/app/assets/javascripts/observability/client.js @@ -246,22 +246,18 @@ async function fetchOperations(operationsUrl, serviceName) { } } -async function fetchMetrics() { - // TODO replace mocks with API calls https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2469 - /* eslint-disable @gitlab/require-i18n-strings */ - return { - metrics: [ - { name: 'metric A', description: 'a counter metric called A', type: 'COUNTER' }, - { name: 'metric B', description: 'a gauge metric called B', type: 'GAUGE' }, - { name: 'metric C', description: 'a histogram metric called C', type: 'HISTOGRAM' }, - { - name: 'metric D', - description: 'a exp histogram metric called D', - type: 'EXPONENTIAL HISTOGRAM', - }, - ], - }; - /* eslint-enable @gitlab/require-i18n-strings */ +async function fetchMetrics(metricsUrl) { + try { + const { data } = await axios.get(metricsUrl, { + withCredentials: true, + }); + if (!Array.isArray(data.metrics)) { + throw new Error('metrics are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings + } + return data; + } catch (e) { + return reportErrorAndThrow(e); + } } export function buildClient(options) { diff --git a/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql b/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql index 7b37186ba1a..a0b2a639401 100644 --- a/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql +++ b/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql @@ -3,7 +3,10 @@ query getOrganizationUsers($id: OrganizationsOrganizationID!) { id organizationUsers { nodes { - badges + badges { + text + variant + } id user { id diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js index c03c00c06aa..bba8e1f7ba5 100644 --- a/app/assets/javascripts/persistent_user_callouts.js +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -23,6 +23,7 @@ const PERSISTENT_USER_CALLOUTS = [ '.js-geo-migrate-hashed-storage-callout', '.js-unlimited-members-during-trial-alert', '.js-branch-rules-info-callout', + '.js-new-nav-for-everyone-callout', '.js-namespace-over-storage-users-combined-alert', ]; diff --git a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue index aa30192b74b..2fc1f99c183 100644 --- a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue +++ b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue @@ -5,9 +5,9 @@ import { INTEGRATION_VIEW_CONFIGS, i18n } from '../constants'; import IntegrationView from './integration_view.vue'; function updateClasses(bodyClasses = '', applicationTheme, layout) { - // Remove body class for any previous theme, re-add current one - document.body.classList.remove(...bodyClasses.split(' ')); - document.body.classList.add(applicationTheme); + // Remove documentElement class for any previous theme, re-add current one + document.documentElement.classList.remove(...bodyClasses.split(' ')); + document.documentElement.classList.add(applicationTheme); // Toggle container-fluid class if (layout === 'fluid') { diff --git a/app/assets/javascripts/projects/settings/init_access_dropdown.js b/app/assets/javascripts/projects/settings/init_access_dropdown.js index 102b1846453..b02a33675ee 100644 --- a/app/assets/javascripts/projects/settings/init_access_dropdown.js +++ b/app/assets/javascripts/projects/settings/init_access_dropdown.js @@ -22,6 +22,7 @@ export const initAccessDropdown = (el, options) => { data() { return { preselected }; }, + disabled, methods: { setPreselectedItems(items) { this.preselected = items; diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js index 29034b3bc0e..66da3de516a 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js @@ -6,6 +6,10 @@ import { initToggle } from '~/toggles'; import { initAccessDropdown } from '~/projects/settings/init_access_dropdown'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; +const isDropdownDisabled = (dropdown) => { + return dropdown?.$options.disabled === ''; +}; + export default class ProtectedBranchEdit { constructor(options) { this.hasLicense = options.hasLicense; @@ -104,6 +108,9 @@ export default class ProtectedBranchEdit { } initSelectedItems(dropdown, accessLevel) { + if (isDropdownDisabled(dropdown)) { + return; + } this.selectedItems[accessLevel] = dropdown.preselected.map((item) => { if (item.type === LEVEL_TYPES.USER) return { id: item.id, user_id: item.user_id }; if (item.type === LEVEL_TYPES.ROLE) return { id: item.id, access_level: item.access_level }; @@ -183,7 +190,10 @@ export default class ProtectedBranchEdit { }; }); - this.selectedItems[accessLevel] = itemsToAdd; - this[`${accessLevel}_dropdown`]?.setPreselectedItems(itemsToAdd); + const dropdown = this[`${accessLevel}_dropdown`]; + if (!isDropdownDisabled(dropdown)) { + this.selectedItems[accessLevel] = itemsToAdd; + dropdown?.setPreselectedItems(itemsToAdd); + } } } diff --git a/app/assets/javascripts/super_sidebar/components/user_menu.vue b/app/assets/javascripts/super_sidebar/components/user_menu.vue index 891e883b6c0..5712b716f48 100644 --- a/app/assets/javascripts/super_sidebar/components/user_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/user_menu.vue @@ -8,7 +8,6 @@ import { } from '@gitlab/ui'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { s__, __, sprintf } from '~/locale'; -import NewNavToggle from '~/nav/components/new_nav_toggle.vue'; import Tracking from '~/tracking'; import PersistentUserCallout from '~/persistent_user_callout'; import { USER_MENU_TRACKING_DEFAULTS, DROPDOWN_Y_OFFSET, IMPERSONATING_OFFSET } from '../constants'; @@ -39,14 +38,13 @@ export default { GlDisclosureDropdownGroup, GlDisclosureDropdownItem, GlButton, - NewNavToggle, UserMenuProfileItem, }, directives: { SafeHtml, }, mixins: [Tracking.mixin()], - inject: ['toggleNewNavEndpoint', 'isImpersonating'], + inject: ['isImpersonating'], props: { data: { required: true, @@ -301,13 +299,6 @@ export default { /> </gl-disclosure-dropdown-group> - <gl-disclosure-dropdown-group bordered> - <template #group-label> - <span class="gl-font-sm">{{ $options.i18n.newNavigation.sectionTitle }}</span> - </template> - <new-nav-toggle :endpoint="toggleNewNavEndpoint" enabled new-navigation /> - </gl-disclosure-dropdown-group> - <gl-disclosure-dropdown-group v-if="data.can_sign_out" bordered diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js index d0d98ef3808..9e540175b48 100644 --- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js +++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js @@ -66,13 +66,7 @@ export const initSuperSidebar = () => { if (!el) return false; - const { - rootPath, - sidebar, - toggleNewNavEndpoint, - forceDesktopExpandedSidebar, - commandPalette, - } = el.dataset; + const { rootPath, sidebar, forceDesktopExpandedSidebar, commandPalette } = el.dataset; bindSuperSidebarCollapsedEvents(forceDesktopExpandedSidebar); initSuperSidebarCollapsedState(parseBoolean(forceDesktopExpandedSidebar)); @@ -98,7 +92,6 @@ export const initSuperSidebar = () => { name: 'SuperSidebarRoot', provide: { rootPath, - toggleNewNavEndpoint, isImpersonating, ...getTrialStatusWidgetData(sidebarData), commandPaletteCommands, diff --git a/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue b/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue index f62bfb551df..70daac311c7 100644 --- a/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue @@ -1,11 +1,5 @@ <script> -import { - GlDropdown, - GlDropdownItem, - GlDropdownText, - GlSearchBoxByType, - GlSprintf, -} from '@gitlab/ui'; +import { GlDisclosureDropdown, GlIcon, GlSearchBoxByType, GlSprintf } from '@gitlab/ui'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import { __, n__, s__, sprintf } from '~/locale'; @@ -16,12 +10,16 @@ export const i18n = { searchFiles: __('Search files'), }; +const variantCssColorMap = { + success: 'gl-text-green-500', + danger: 'gl-text-red-500', +}; + export default { i18n, components: { - GlDropdown, - GlDropdownItem, - GlDropdownText, + GlDisclosureDropdown, + GlIcon, GlSearchBoxByType, GlSprintf, }, @@ -54,6 +52,15 @@ export default { ? fuzzaldrinPlus.filter(this.files, this.search, { key: 'name' }) : this.files; }, + dropdownItems() { + return this.filteredFiles.map((file) => { + return { + ...file, + text: file.name || this.$options.i18n.noFileNameAvailable, + iconColor: variantCssColorMap[file.iconColor], + }; + }); + }, messageChanged() { return sprintf( n__( @@ -64,21 +71,21 @@ export default { { count: this.changed }, ); }, - - additionsText() { - return n__('Diffs|%d addition', 'Diffs|%d additions', this.added); - }, - deletionsText() { - return n__('Diffs|%d deletion', 'Diffs|%d deletions', this.deleted); - }, }, methods: { - jumpToFile(fileHash) { - window.location.hash = fileHash; - }, focusInput() { this.$refs.search.focusInput(); }, + focusFirstItem() { + if (!this.filteredFiles.length) return; + this.$el.querySelector('.gl-new-dropdown-item:first-child').focus(); + }, + additionsText(numberOfChanges = this.added) { + return n__('Diffs|%d addition', 'Diffs|%d additions', numberOfChanges); + }, + deletionsText(numberOfChanges = this.deleted) { + return n__('Diffs|%d deletion', 'Diffs|%d deletions', numberOfChanges); + }, }, }; </script> @@ -87,15 +94,15 @@ export default { <div> <gl-sprintf :message="messageChanged"> <template #dropdown="{ content: dropdownText }"> - <gl-dropdown + <gl-disclosure-dropdown + :toggle-text="dropdownText" + :items="dropdownItems" category="tertiary" variant="confirm" - :text="dropdownText" data-testid="diff-stats-dropdown" class="gl-vertical-align-baseline" toggle-class="gl-px-0! gl-font-weight-bold!" - menu-class="gl-w-auto!" - no-flip + fluid-width @shown="focusInput" > <template #header> @@ -103,35 +110,38 @@ export default { ref="search" v-model.trim="search" :placeholder="$options.i18n.searchFiles" + class="gl-mx-3 gl-my-4" + @keydown.down="focusFirstItem" /> + <span v-if="!filteredFiles.length" class="gl-mx-3"> + {{ $options.i18n.noFilesFound }} + </span> </template> - <gl-dropdown-item - v-for="file in filteredFiles" - :key="file.href" - :icon-name="file.icon" - :icon-color="file.iconColor" - @click="jumpToFile(file.href)" - > - <div class="gl-display-flex"> - <span v-if="file.name" class="gl-font-weight-bold gl-mr-3 gl-text-truncate">{{ - file.name - }}</span> - <span v-else class="gl-mr-3 gl-font-weight-bold gl-font-style-italic gl-gray-400">{{ - $options.i18n.noFileNameAvailable - }}</span> - <span class="gl-ml-auto gl-white-space-nowrap"> - <span class="gl-text-green-600">+{{ file.added }}</span> - <span class="gl-text-red-500">-{{ file.removed }}</span> - </span> + <template #list-item="{ item }"> + <div class="gl-display-flex gl-gap-3 gl-align-items-center"> + <gl-icon :name="item.icon" :class="item.iconColor" /> + <div class="gl-flex-grow-1"> + <div class="gl-display-flex"> + <span + class="gl-font-weight-bold gl-mr-3 gl-flex-grow-1" + :class="item.name ? 'gl-text-truncate' : 'gl-font-style-italic gl-gray-400'" + >{{ item.text }}</span + > + <span class="gl-ml-auto gl-white-space-nowrap" aria-hidden="true"> + <span class="gl-text-green-600">+{{ item.added }}</span> + <span class="gl-text-red-500">-{{ item.removed }}</span> + </span> + <span class="gl-sr-only" + >{{ additionsText(item.added) }}, {{ deletionsText(item.removed) }}</span + > + </div> + <div class="gl-text-gray-700 gl-overflow-hidden gl-text-overflow-ellipsis"> + {{ item.path }} + </div> + </div> </div> - <div class="gl-text-gray-700 gl-overflow-hidden gl-text-overflow-ellipsis"> - {{ file.path }} - </div> - </gl-dropdown-item> - <gl-dropdown-text v-if="!filteredFiles.length"> - {{ $options.i18n.noFilesFound }} - </gl-dropdown-text> - </gl-dropdown> + </template> + </gl-disclosure-dropdown> </template> </gl-sprintf> <span @@ -140,12 +150,20 @@ export default { > <gl-sprintf :message="$options.i18n.messageAdditionsDeletions"> <template #additions> - <span class="gl-text-green-600 gl-font-weight-bold">{{ additionsText }}</span> + <span class="gl-text-green-600 gl-font-weight-bold">{{ additionsText() }}</span> </template> <template #deletions> - <span class="gl-text-red-500 gl-font-weight-bold">{{ deletionsText }}</span> + <span class="gl-text-red-500 gl-font-weight-bold">{{ deletionsText() }}</span> </template> </gl-sprintf> </span> </div> </template> + +<style scoped> +/* TODO: Use max-height prop when gitlab-ui got updated. +See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2374 */ +::v-deep .gl-new-dropdown-inner { + max-height: 310px; +} +</style> diff --git a/app/assets/javascripts/vue_shared/components/list_selector/constants.js b/app/assets/javascripts/vue_shared/components/list_selector/constants.js index 2e58527a2ea..cff9c56a1c0 100644 --- a/app/assets/javascripts/vue_shared/components/list_selector/constants.js +++ b/app/assets/javascripts/vue_shared/components/list_selector/constants.js @@ -1,6 +1,6 @@ import { __ } from '~/locale'; export const CONFIG = { - users: { title: __('Users'), icon: 'user', filterKey: 'username' }, + users: { title: __('Users'), icon: 'user', filterKey: 'username', showNamespaceDropdown: true }, groups: { title: __('Groups'), icon: 'group', filterKey: 'name' }, }; diff --git a/app/assets/javascripts/vue_shared/components/list_selector/index.vue b/app/assets/javascripts/vue_shared/components/list_selector/index.vue index 6813d9ca077..b8480a0c496 100644 --- a/app/assets/javascripts/vue_shared/components/list_selector/index.vue +++ b/app/assets/javascripts/vue_shared/components/list_selector/index.vue @@ -1,13 +1,23 @@ <script> import { GlCard, GlIcon, GlCollapsibleListbox, GlSearchBoxByType } from '@gitlab/ui'; -import usersAutocompleteQuery from '~/graphql_shared/queries/users_autocomplete.query.graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import { createAlert } from '~/alert'; +import { __ } from '~/locale'; import groupsAutocompleteQuery from '~/graphql_shared/queries/groups_autocomplete.query.graphql'; +import Api from '~/api'; import UserItem from './user_item.vue'; import GroupItem from './group_item.vue'; import { CONFIG } from './constants'; +const I18N = { + allGroups: __('All groups'), + projectGroups: __('Project groups'), + apiErrorMessage: __('An error occurred while fetching. Please try again.'), +}; + export default { name: 'ListSelector', + i18n: I18N, components: { GlCard, GlIcon, @@ -33,11 +43,16 @@ export default { required: false, default: null, }, + groupPath: { + type: String, + required: false, + default: null, + }, }, data() { return { searchValue: '', - isProject: true, // TODO: implement a way to distinguish between project/group + isProjectNamespace: 'true', selected: [], items: [], }; @@ -46,39 +61,44 @@ export default { config() { return CONFIG[this.type]; }, - searchItems() { - return this.items; - }, isUserVariant() { return this.type === 'users'; }, component() { return this.isUserVariant ? UserItem : GroupItem; }, + namespaceDropdownText() { + return parseBoolean(this.isProjectNamespace) + ? this.$options.i18n.projectGroups + : this.$options.i18n.allGroups; + }, }, methods: { async handleSearchInput(search) { this.$refs.results.open(); - if (this.isUserVariant) { - this.items = await this.fetchUsersBySearchTerm(search); - } else { - this.items = await this.fetchGroupsBySearchTerm(search); + + try { + if (this.isUserVariant) { + this.items = await this.fetchUsersBySearchTerm(search); + } else { + this.items = await this.fetchGroupsBySearchTerm(search); + } + } catch (e) { + createAlert({ + message: this.$options.i18n.apiErrorMessage, + }); } }, - fetchUsersBySearchTerm(search) { - const namespace = this.isProject ? 'project' : 'group'; - return this.$apollo - .query({ - query: usersAutocompleteQuery, - variables: { fullPath: this.projectPath, search, isProject: this.isProject }, - }) - .then(({ data }) => - data[namespace]?.autocompleteUsers.map((user) => ({ - text: user.name, - value: user.username, - ...user, - })), - ); + async fetchUsersBySearchTerm(search) { + let users = []; + if (parseBoolean(this.isProjectNamespace)) { + users = await Api.projectUsers(this.projectPath, search); + } else { + const groupMembers = await Api.groupMembers(this.groupPath, { query: search }); + users = groupMembers?.data || []; + } + + return users?.map((user) => ({ text: user.name, value: user.username, ...user })); }, fetchGroupsBySearchTerm(search) { return this.$apollo @@ -95,7 +115,7 @@ export default { ); }, getItemByKey(key) { - return this.searchItems.find((item) => item[this.config.filterKey] === key); + return this.items.find((item) => item[this.config.filterKey] === key); }, handleSelectItem(key) { this.$emit('select', this.getItemByKey(key)); @@ -103,7 +123,15 @@ export default { handleDeleteItem(key) { this.$emit('delete', key); }, + handleSelectNamespace() { + this.items = []; + this.searchValue = ''; + }, }, + namespaceOptions: [ + { text: I18N.projectGroups, value: 'true' }, + { text: I18N.allGroups, value: 'false' }, + ], }; </script> @@ -118,28 +146,38 @@ export default { ></template > - <gl-collapsible-listbox - ref="results" - v-model="selected" - class="list-selector gl-mb-4 gl-display-block" - :items="searchItems" - multiple - @shown="$refs.search.focusInput()" - > - <template #toggle> - <gl-search-box-by-type - ref="search" - v-model="searchValue" - autofocus - debounce="500" - @input="handleSearchInput" - /> - </template> + <div class="gl-display-flex gl-gap-3" :class="{ 'gl-mb-4': selectedItems.length }"> + <gl-collapsible-listbox + ref="results" + v-model="selected" + class="list-selector gl-display-block gl-flex-grow-1" + :items="items" + multiple + @shown="$refs.search.focusInput()" + > + <template #toggle> + <gl-search-box-by-type + ref="search" + v-model="searchValue" + autofocus + debounce="500" + @input="handleSearchInput" + /> + </template> + + <template #list-item="{ item }"> + <component :is="component" :data="item" @select="handleSelectItem" /> + </template> + </gl-collapsible-listbox> - <template #list-item="{ item }"> - <component :is="component" :data="item" @select="handleSelectItem" /> - </template> - </gl-collapsible-listbox> + <gl-collapsible-listbox + v-if="config.showNamespaceDropdown" + v-model="isProjectNamespace" + :toggle-text="namespaceDropdownText" + :items="$options.namespaceOptions" + @select="handleSelectNamespace" + /> + </div> <component :is="component" diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_body.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_body.vue index 45fde45f516..dae3ddfe016 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_body.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_body.vue @@ -74,6 +74,11 @@ export default { required: false, default: 0, }, + workspaceType: { + type: String, + required: false, + default: '', + }, }, computed: { isUpdated() { @@ -161,6 +166,7 @@ export default { :issuable="issuable" :status-icon="statusIcon" :enable-edit="enableEdit" + :workspace-type="workspaceType" @edit-issuable="$emit('edit-issuable', $event)" > <template #status-badge> diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue index 3878c16c8d0..040f49c7c25 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue @@ -147,6 +147,7 @@ export default { :description-help-path="descriptionHelpPath" :task-list-update-path="taskListUpdatePath" :task-list-lock-version="taskListLockVersion" + :workspace-type="workspaceType" @edit-issuable="$emit('edit-issuable', $event)" @task-list-update-success="$emit('task-list-update-success', $event)" @task-list-update-failure="$emit('task-list-update-failure')" diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue index 3aef7d141e0..5387e39e3eb 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue @@ -1,5 +1,6 @@ <script> import { GlIcon, GlBadge, GlButton, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui'; +import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { STATUS_OPEN } from '~/issues/constants'; import { __ } from '~/locale'; @@ -13,6 +14,7 @@ export default { GlBadge, GlButton, GlIntersectionObserver, + ConfidentialityBadge, }, directives: { GlTooltip: GlTooltipDirective, @@ -31,6 +33,11 @@ export default { type: Boolean, required: true, }, + workspaceType: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -89,6 +96,12 @@ export default { <slot name="status-badge"></slot> </span> </gl-badge> + <confidentiality-badge + v-if="issuable.confidential" + class="gl-white-space-nowrap gl-mr-3 gl-align-self-center" + :issuable-type="issuable.type" + :workspace-type="workspaceType" + /> <p class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0" :title="issuable.title" diff --git a/app/assets/javascripts/work_items/components/work_item_type_icon.vue b/app/assets/javascripts/work_items/components/work_item_type_icon.vue index 76a73093206..5426f3965b3 100644 --- a/app/assets/javascripts/work_items/components/work_item_type_icon.vue +++ b/app/assets/javascripts/work_items/components/work_item_type_icon.vue @@ -36,11 +36,6 @@ export default { return this.workItemType.toUpperCase().split(' ').join('_'); }, iconName() { - // TODO Delete this conditional once we have an `issue-type-epic` icon - if (this.workItemIconName === 'issue-type-epic') { - return 'epic'; - } - return ( this.workItemIconName || WORK_ITEMS_TYPE_MAP[this.workItemTypeUppercase]?.icon || diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 2f2401bd9b3..e2dbfeb55a5 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -35,6 +35,7 @@ export const WORK_ITEM_TYPE_ENUM_TEST_CASE = 'TEST_CASE'; export const WORK_ITEM_TYPE_ENUM_REQUIREMENTS = 'REQUIREMENTS'; export const WORK_ITEM_TYPE_ENUM_OBJECTIVE = 'OBJECTIVE'; export const WORK_ITEM_TYPE_ENUM_KEY_RESULT = 'KEY_RESULT'; +export const WORK_ITEM_TYPE_ENUM_EPIC = 'EPIC'; export const WORK_ITEM_TYPE_VALUE_EPIC = 'Epic'; export const WORK_ITEM_TYPE_VALUE_INCIDENT = 'Incident'; @@ -185,6 +186,11 @@ export const WORK_ITEMS_TYPE_MAP = { name: s__('WorkItem|Key result'), value: WORK_ITEM_TYPE_VALUE_KEY_RESULT, }, + [WORK_ITEM_TYPE_ENUM_EPIC]: { + icon: `epic`, + name: s__('WorkItem|Epic'), + value: WORK_ITEM_TYPE_VALUE_EPIC, + }, }; export const WORK_ITEMS_TREE_TEXT_MAP = { |