diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-04 18:10:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-04 18:10:27 +0300 |
commit | d984d4a092018d86eec724a06ce2e6c066c3fb44 (patch) | |
tree | b72454a613480658d9b2b14b74eb7b8c75858544 /app | |
parent | 30a8e054751fe9020a9ed526434db83af35b4718 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
7 files changed, 241 insertions, 35 deletions
diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js index de0334b4ffe..98888f9f9b2 100644 --- a/app/assets/javascripts/issues/create_merge_request_dropdown.js +++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js @@ -137,8 +137,6 @@ export default class CreateMergeRequestDropdown { this.refInput.addEventListener('keyup', this.onChangeInput.bind(this)); // Detect when user clicks inside the input to apply the suggested ref this.refInput.addEventListener('click', this.onChangeInput.bind(this)); - // Detect when user clicks outside the input to apply the suggested ref - this.refInput.addEventListener('blur', this.onChangeInput.bind(this)); // Detect when user presses tab to apply the suggested ref this.refInput.addEventListener('keydown', CreateMergeRequestDropdown.processTab.bind(this)); } @@ -178,8 +176,16 @@ export default class CreateMergeRequestDropdown { createBranch(navigateToBranch = true) { this.isCreatingBranch = true; + const endpoint = createEndpoint( + this.projectPath, + mergeUrlParams( + { ref: this.refInput.value, branch_name: this.branchInput.value }, + this.createBranchPath, + ), + ); + return axios - .post(createEndpoint(this.projectPath, this.createBranchPath), { + .post(endpoint, { confidential_issue_project_id: canCreateConfidentialMergeRequest() ? this.projectId : null, }) .then(({ data }) => { @@ -407,9 +413,6 @@ export default class CreateMergeRequestDropdown { // If the input is empty, use the original value generated by the backend. if (!value) { - this.createBranchPath = this.wrapperEl.dataset.createBranchPath; - this.createMrPath = this.wrapperEl.dataset.createMrPath; - if (target === INPUT_TARGET_BRANCH) { this.branchIsValid = true; } else { @@ -539,7 +542,6 @@ export default class CreateMergeRequestDropdown { updateBranchName(suggestedBranchName) { this.branchInput.value = suggestedBranchName; this.updateInputState(INPUT_TARGET_BRANCH, suggestedBranchName, ''); - this.updateCreatePaths(INPUT_TARGET_BRANCH, suggestedBranchName); } updateInputState(target, ref, result) { @@ -561,7 +563,6 @@ export default class CreateMergeRequestDropdown { if (ref === result) { this.refIsValid = true; this.showAvailableMessage(INPUT_TARGET_REF); - this.updateCreatePaths(INPUT_TARGET_REF, ref); } else { this.refIsValid = false; this.refInput.dataset.value = ref; @@ -585,7 +586,6 @@ export default class CreateMergeRequestDropdown { // Or user typed input contains invalid chars, // that means a new branch cannot be created as it already exists. this.showAvailableMessage(INPUT_TARGET_BRANCH, VALIDATION_TYPE_BRANCH_UNAVAILABLE); - this.updateCreatePaths(INPUT_TARGET_BRANCH, ref); } else if (isInvalidString) { this.branchIsValid = false; this.showNotAvailableMessage(INPUT_TARGET_BRANCH, VALIDATION_TYPE_INVALID_CHARS); @@ -594,22 +594,4 @@ export default class CreateMergeRequestDropdown { this.showNotAvailableMessage(INPUT_TARGET_BRANCH); } } - - // target - 'branch' or 'ref' - // ref - string - the new value to use as branch or ref - updateCreatePaths(target, ref) { - const pathReplacement = `$1${encodeURIComponent(ref)}`; - - this.createBranchPath = this.createBranchPath.replace( - this.regexps[target].createBranchPath, - pathReplacement, - ); - this.createMrPath = this.createMrPath.replace( - this.regexps[target].createMrPath, - pathReplacement, - ); - - this.wrapperEl.dataset.createBranchPath = this.createBranchPath; - this.wrapperEl.dataset.createMrPath = this.createMrPath; - } } diff --git a/app/assets/javascripts/super_sidebar/components/flyout_menu.vue b/app/assets/javascripts/super_sidebar/components/flyout_menu.vue index 285fa8ee90b..fa7960da2f4 100644 --- a/app/assets/javascripts/super_sidebar/components/flyout_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/flyout_menu.vue @@ -1,5 +1,5 @@ <script> -import { computePosition, autoUpdate, flip, shift } from '@floating-ui/dom'; +import { computePosition, autoUpdate, offset, flip, shift } from '@floating-ui/dom'; import NavItem from './nav_item.vue'; export default { @@ -22,7 +22,7 @@ export default { function updatePosition() { return computePosition(target, flyout, { - middleware: [flip(), shift()], + middleware: [offset({ alignmentAxis: -12 }), flip(), shift()], placement: 'right-start', strategy: 'fixed', }).then(({ x, y }) => { @@ -44,13 +44,13 @@ export default { <template> <div :id="`${targetId}-flyout`" - class="gl-fixed gl-pl-3 gl-z-index-9999 gl-max-h-full gl-overflow-y-auto" + class="gl-fixed gl-p-4 gl-mx-n1 gl-z-index-9999 gl-max-h-full gl-overflow-y-auto" @mouseover="$emit('mouseover')" @mouseleave="$emit('mouseleave')" > <ul v-if="items.length > 0" - class="gl-min-w-20 gl-max-w-34 gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-100 gl-shadow gl-bg-white gl-p-2 gl-pb-1 gl-list-style-none" + class="gl-min-w-20 gl-max-w-34 gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-100 gl-shadow-md gl-bg-white gl-p-2 gl-pb-1 gl-list-style-none" > <nav-item v-for="item of items" diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 842893af931..79d476ee74b 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -109,7 +109,8 @@ export default { return { 'gl-px-2 gl-mx-2 gl-line-height-normal': this.isSubitem, 'gl-px-3': !this.isSubitem, - 'gl-pl-5!': this.isFlyout, + 'gl-pl-5! gl-rounded-small': this.isFlyout, + 'gl-rounded-base': !this.isFlyout, [this.item.link_classes]: this.item.link_classes, ...this.linkClasses, }; @@ -127,7 +128,7 @@ export default { :is="navItemLinkComponent" #default="{ isActive }" v-bind="linkProps" - class="nav-item-link gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-min-h-7 gl-gap-3 gl-mb-1 gl-py-2 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus" + class="nav-item-link gl-relative gl-display-flex gl-align-items-center gl-min-h-7 gl-gap-3 gl-mb-1 gl-py-2 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus" :class="computedLinkClasses" data-qa-selector="nav_item_link" data-testid="nav-item-link" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue index 258fa4edcda..9bb39ba22e0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue @@ -5,8 +5,9 @@ export default { import( '~/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue' ), - MrTerraformWidget: () => import('~/vue_merge_request_widget/extensions/terraform/index.vue'), + MrCodeQualityWidget: () => + import('~/vue_merge_request_widget/extensions/code_quality/index.vue'), }, props: { @@ -21,8 +22,14 @@ export default { return this.mr.terraformReportsPath && 'MrTerraformWidget'; }, + codeQualityWidget() { + return this.mr.codequalityReportsPath ? 'MrCodeQualityWidget' : undefined; + }, + widgets() { - return [this.terraformPlansWidget, 'MrSecurityWidget'].filter((w) => w); + return [this.codeQualityWidget, this.terraformPlansWidget, 'MrSecurityWidget'].filter( + (w) => w, + ); }, }, }; diff --git a/app/assets/javascripts/vue_shared/components/groups_list/groups_list.stories.js b/app/assets/javascripts/vue_shared/components/groups_list/groups_list.stories.js new file mode 100644 index 00000000000..235523054c3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/groups_list/groups_list.stories.js @@ -0,0 +1,19 @@ +import { groups } from 'jest/vue_shared/components/groups_list/mock_data'; +import GroupsList from './groups_list.vue'; + +export default { + component: GroupsList, + title: 'vue_shared/groups_list', +}; + +const Template = (args, { argTypes }) => ({ + components: { GroupsList }, + props: Object.keys(argTypes), + template: '<groups-list v-bind="$props" />', +}); + +export const Default = Template.bind({}); +Default.args = { + groups, + showGroupIcon: true, +}; diff --git a/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue b/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue new file mode 100644 index 00000000000..7da45169fee --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue @@ -0,0 +1,29 @@ +<script> +import GroupsListItem from './groups_list_item.vue'; + +export default { + components: { GroupsListItem }, + props: { + groups: { + type: Array, + required: true, + }, + showGroupIcon: { + type: Boolean, + required: false, + default: false, + }, + }, +}; +</script> + +<template> + <ul class="gl-p-0 gl-list-style-none"> + <groups-list-item + v-for="group in groups" + :key="group.id" + :group="group" + :show-group-icon="showGroupIcon" + /> + </ul> +</template> diff --git a/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue b/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue new file mode 100644 index 00000000000..8a301cd0dd0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue @@ -0,0 +1,168 @@ +<script> +import { GlAvatarLabeled, GlIcon, GlTooltipDirective, GlTruncateText } from '@gitlab/ui'; + +import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/visibility_level/constants'; +import { ACCESS_LEVEL_LABELS } from '~/access_level/constants'; +import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue'; +import { __ } from '~/locale'; +import { numberToMetricPrefix } from '~/lib/utils/number_utils'; +import SafeHtml from '~/vue_shared/directives/safe_html'; + +export default { + i18n: { + subgroups: __('Subgroups'), + projects: __('Projects'), + directMembers: __('Direct members'), + showMore: __('Show more'), + showLess: __('Show less'), + }, + avatarSize: { default: 32, md: 48 }, + safeHtmlConfig: { + ADD_TAGS: ['gl-emoji'], + }, + components: { + GlAvatarLabeled, + GlIcon, + UserAccessRoleBadge, + GlTruncateText, + }, + directives: { + GlTooltip: GlTooltipDirective, + SafeHtml, + }, + props: { + group: { + type: Object, + required: true, + }, + showGroupIcon: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + visibility() { + return this.group.visibility; + }, + visibilityIcon() { + return VISIBILITY_TYPE_ICON[this.visibility]; + }, + visibilityTooltip() { + return GROUP_VISIBILITY_TYPE[this.visibility]; + }, + accessLevel() { + return this.group.accessLevel?.integerValue; + }, + accessLevelLabel() { + return ACCESS_LEVEL_LABELS[this.accessLevel]; + }, + shouldShowAccessLevel() { + return this.accessLevel !== undefined; + }, + groupIconName() { + return this.group.parent ? 'subgroup' : 'group'; + }, + statsPadding() { + return this.showGroupIcon ? 'gl-pl-11' : 'gl-pl-8'; + }, + descendantGroupsCount() { + return numberToMetricPrefix(this.group.descendantGroupsCount); + }, + projectsCount() { + return numberToMetricPrefix(this.group.projectsCount); + }, + groupMembersCount() { + return numberToMetricPrefix(this.group.groupMembersCount); + }, + }, +}; +</script> + +<template> + <li class="groups-list-item gl-py-5 gl-md-display-flex gl-align-items-center gl-border-b"> + <div class="gl-display-flex gl-flex-grow-1"> + <gl-icon + v-if="showGroupIcon" + class="gl-mr-3 gl-mt-3 gl-md-mt-5 gl-flex-shrink-0 gl-text-secondary" + :name="groupIconName" + /> + <gl-avatar-labeled + :entity-id="group.id" + :entity-name="group.fullName" + :label="group.fullName" + :label-link="group.webUrl" + shape="rect" + :size="$options.avatarSize" + > + <template #meta> + <div class="gl-px-2"> + <div class="gl-mx-n2 gl-display-flex gl-align-items-center gl-flex-wrap"> + <div class="gl-px-2"> + <gl-icon + v-if="visibility" + v-gl-tooltip="visibilityTooltip" + :name="visibilityIcon" + class="gl-text-secondary" + /> + </div> + <div class="gl-px-2"> + <user-access-role-badge v-if="shouldShowAccessLevel">{{ + accessLevelLabel + }}</user-access-role-badge> + </div> + </div> + </div> + </template> + <gl-truncate-text + v-if="group.descriptionHtml" + :lines="2" + :mobile-lines="2" + :show-more-text="$options.i18n.showMore" + :show-less-text="$options.i18n.showLess" + class="gl-mt-2" + > + <div + v-safe-html:[$options.safeHtmlConfig]="group.descriptionHtml" + class="gl-font-sm md" + data-testid="group-description" + ></div> + </gl-truncate-text> + </gl-avatar-labeled> + </div> + <div + class="gl-md-display-flex gl-flex-direction-column gl-align-items-flex-end gl-flex-shrink-0 gl-mt-3 gl-md-pl-0 gl-md-mt-0 gl-md-ml-3" + :class="statsPadding" + > + <div class="gl-display-flex gl-align-items-center gl-gap-x-3"> + <div + v-gl-tooltip="$options.i18n.subgroups" + :aria-label="$options.i18n.subgroups" + class="gl-text-secondary" + data-testid="subgroups-count" + > + <gl-icon name="subgroup" /> + <span>{{ descendantGroupsCount }}</span> + </div> + <div + v-gl-tooltip="$options.i18n.projects" + :aria-label="$options.i18n.projects" + class="gl-text-secondary" + data-testid="projects-count" + > + <gl-icon name="project" /> + <span>{{ projectsCount }}</span> + </div> + <div + v-gl-tooltip="$options.i18n.directMembers" + :aria-label="$options.i18n.directMembers" + class="gl-text-secondary" + data-testid="members-count" + > + <gl-icon name="users" /> + <span>{{ groupMembersCount }}</span> + </div> + </div> + </div> + </li> +</template> |