diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-11 18:10:20 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-11 18:10:20 +0300 |
commit | e3042fc5ced749e693ccef81b3f5838c55d5480c (patch) | |
tree | e004dca26da0ec413d5c9ebf174962a008fde0bb /app | |
parent | c33a9adb709ffb40f816e66eb0c98cc750d6cd43 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
46 files changed, 531 insertions, 133 deletions
diff --git a/app/assets/javascripts/emoji/components/picker.vue b/app/assets/javascripts/emoji/components/picker.vue index 71cabe80529..e08d294b8c5 100644 --- a/app/assets/javascripts/emoji/components/picker.vue +++ b/app/assets/javascripts/emoji/components/picker.vue @@ -79,6 +79,7 @@ export default { :toggle-class="toggleClass" :boundary="getBoundaryElement()" menu-class="dropdown-extended-height" + category="tertiary" no-flip right lazy diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue index b0f19e5b585..93d8bcc4c19 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue @@ -16,13 +16,13 @@ const commentDetailOptions = [ { value: 'standard', label: s__('Integrations|Standard'), - help: s__('Integrations|Includes commit title and branch'), + help: s__('Integrations|Includes commit title and branch.'), }, { value: 'all_details', label: s__('Integrations|All details'), help: s__( - 'Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs', + 'Integrations|Includes Standard, plus the entire commit message, commit hash, and issue IDs', ), }, ]; @@ -144,7 +144,7 @@ export default { label-for="service[trigger]" :description=" s__( - 'Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) is created.', + 'Integrations|When you mention a Jira issue in a commit or merge request, GitLab creates a remote link and comment (if enabled).', ) " > diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index 16c76e048bd..0cc818c6d0e 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -278,7 +278,6 @@ export default { v-if="canResolve" ref="resolveButton" v-gl-tooltip - size="small" category="tertiary" :variant="resolveVariant" :class="{ 'is-disabled': !resolvable, 'is-active': isResolved }" @@ -292,7 +291,7 @@ export default { <template v-if="canAwardEmoji"> <emoji-picker v-if="glFeatures.improvedEmojiPicker" - toggle-class="note-action-button note-emoji-button gl-text-gray-600 gl-m-2 gl-p-0! gl-shadow-none! gl-bg-transparent!" + toggle-class="note-action-button note-emoji-button gl-text-gray-600 gl-m-3 gl-p-0! gl-shadow-none! gl-bg-transparent!" @click="setAwardEmoji" > <template #button-content> @@ -305,10 +304,9 @@ export default { v-else v-gl-tooltip :class="{ 'js-user-authored': isAuthoredByCurrentUser }" - class="note-action-button note-emoji-button add-reaction-button js-add-award js-note-emoji" + class="note-action-button note-emoji-button add-reaction-button btn-icon js-add-award js-note-emoji" category="tertiary" variant="default" - size="small" :title="$options.i18n.addReactionLabel" :aria-label="$options.i18n.addReactionLabel" data-position="right" @@ -336,7 +334,6 @@ export default { :title="$options.i18n.editCommentLabel" :aria-label="$options.i18n.editCommentLabel" icon="pencil" - size="small" category="tertiary" class="note-action-button js-note-edit" data-qa-selector="note_edit_button" @@ -347,7 +344,6 @@ export default { v-gl-tooltip :title="$options.i18n.deleteCommentLabel" :aria-label="$options.i18n.deleteCommentLabel" - size="small" icon="remove" category="tertiary" class="note-action-button js-note-delete" @@ -360,7 +356,6 @@ export default { :title="$options.i18n.moreActionsLabel" :aria-label="$options.i18n.moreActionsLabel" icon="ellipsis_v" - size="small" category="tertiary" class="note-action-button more-actions-toggle" data-toggle="dropdown" diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue index 5ce03091504..0cd2afcf8a0 100644 --- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue +++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue @@ -22,7 +22,6 @@ export default { data-track-event="click_button" data-track-label="reply_comment_button" category="tertiary" - size="small" icon="comment" :title="$options.i18n.buttonText" :aria-label="$options.i18n.buttonText" diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue new file mode 100644 index 00000000000..22c1563350d --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue @@ -0,0 +1,67 @@ +<script> +import { GlCard, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import PipelineVisualReference from '../ui/pipeline_visual_reference.vue'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|🚀 Run your first pipeline'), + firstParagraph: s__( + 'PipelineEditorTutorial|A typical GitLab pipeline consists of three stages: build, test and deploy. Each stage can have one or more jobs.', + ), + secondParagraph: s__( + 'PipelineEditorTutorial|In the example below, %{codeStart}build%{codeEnd} and %{codeStart}deploy%{codeEnd} each contain one job, and %{codeStart}test%{codeEnd} contains two jobs. Your scripts run in jobs like these.', + ), + thirdParagraph: s__( + 'PipelineEditorTutorial|You can use %{linkStart}CI/CD examples and templates%{linkEnd} to get your first %{codeStart}.gitlab-ci.yml%{codeEnd} configuration file started. Your first pipeline runs when you commit the changes.', + ), + note: s__( + 'PipelineEditorTutorial|If you’re using a self-managed GitLab instance, %{linkStart}make sure your instance has runners available.%{linkEnd}', + ), + }, + components: { + GlCard, + GlLink, + GlSprintf, + PipelineVisualReference, + }, + inject: ['ciExamplesHelpPagePath', 'runnerHelpPagePath'], +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p> + <p class="gl-mb-3"> + <gl-sprintf :message="$options.i18n.secondParagraph"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <pipeline-visual-reference /> + <p class="gl-my-3"> + <gl-sprintf :message="$options.i18n.thirdParagraph"> + <template #link="{ content }"> + <gl-link :href="ciExamplesHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <p class="gl-mb-0"> + <gl-sprintf :message="$options.i18n.note"> + <template #link="{ content }"> + <gl-link :href="runnerHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </p> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue new file mode 100644 index 00000000000..3da535f5f94 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue @@ -0,0 +1,35 @@ +<script> +import { GlCard, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|Get started with GitLab CI/CD'), + firstParagraph: s__( + 'PipelineEditorTutorial|GitLab CI/CD can automatically build, test, and deploy your application.', + ), + secondParagraph: s__( + 'PipelineEditorTutorial|The pipeline stages and jobs are defined in a %{codeStart}.gitlab-ci.yml%{codeEnd} file. You can edit, visualize and validate the syntax in this file by using the Pipeline Editor.', + ), + }, + components: { + GlCard, + GlSprintf, + }, +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p> + <p class="gl-mb-0"> + <gl-sprintf :message="$options.i18n.secondParagraph"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue new file mode 100644 index 00000000000..f714f6411f1 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue @@ -0,0 +1,75 @@ +<script> +import { GlCard, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|⚙️ Pipeline configuration reference'), + firstParagraph: s__('PipelineEditorTutorial|Resources to help with your CI/CD configuration:'), + browseExamples: s__( + 'PipelineEditorTutorial|Browse %{linkStart}CI/CD examples and templates%{linkEnd}', + ), + viewSyntaxRef: s__( + 'PipelineEditorTutorial|View %{linkStart}.gitlab-ci.yml syntax reference%{linkEnd}', + ), + learnMore: s__( + 'PipelineEditorTutorial|Learn more about %{linkStart}GitLab CI/CD concepts%{linkEnd}', + ), + needs: s__( + 'PipelineEditorTutorial|Make your pipeline more efficient with the %{linkStart}Needs keyword%{linkEnd}', + ), + }, + components: { + GlCard, + GlLink, + GlSprintf, + }, + inject: ['ciExamplesHelpPagePath', 'ciHelpPagePath', 'needsHelpPagePath', 'ymlHelpPagePath'], +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p> + <ul> + <li> + <gl-sprintf :message="$options.i18n.browseExamples"> + <template #link="{ content }"> + <gl-link :href="ciExamplesHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.viewSyntaxRef"> + <template #link="{ content }"> + <gl-link :href="ymlHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.learnMore"> + <template #link="{ content }"> + <gl-link :href="ciHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.needs"> + <template #link="{ content }"> + <gl-link :href="needsHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + </ul> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue new file mode 100644 index 00000000000..512414f0246 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue @@ -0,0 +1,24 @@ +<script> +import { GlCard } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|💡 Tip: Visualize and validate your pipeline'), + firstParagraph: s__( + 'PipelineEditorTutorial|Use the Visualize and Lint tabs in the Pipeline Editor to visualize your pipeline and check for any errors or warnings before committing your changes.', + ), + }, + components: { + GlCard, + }, +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-0">{{ $options.i18n.firstParagraph }}</p> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue index ef5be8abf9a..e1f38b4332b 100644 --- a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue +++ b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue @@ -1,6 +1,10 @@ <script> import { GlButton, GlIcon } from '@gitlab/ui'; import { __ } from '~/locale'; +import FirstPipelineCard from './cards/first_pipeline_card.vue'; +import GettingStartedCard from './cards/getting_started_card.vue'; +import PipelineConfigReferenceCard from './cards/pipeline_config_reference_card.vue'; +import VisualizeAndLintCard from './cards/visualize_and_lint_card.vue'; export default { width: { @@ -11,6 +15,10 @@ export default { toggleTxt: __('Collapse'), }, components: { + FirstPipelineCard, + GettingStartedCard, + PipelineConfigReferenceCard, + VisualizeAndLintCard, GlButton, GlIcon, }, @@ -55,7 +63,7 @@ export default { <template> <aside aria-live="polite" - class="gl-fixed gl-right-0 gl-h-full gl-bg-gray-10 gl-transition-medium gl-border-l-solid gl-border-1 gl-border-gray-100" + class="gl-fixed gl-right-0 gl-bg-gray-10 gl-shadow-drawer gl-transition-medium gl-border-l-solid gl-border-1 gl-border-gray-100 gl-h-full gl-z-index-9999 gl-overflow-y-auto" :style="rootStyle" > <gl-button @@ -63,14 +71,19 @@ export default { class="gl-w-full gl-h-9 gl-rounded-0! gl-border-none! gl-border-b-solid! gl-border-1! gl-border-gray-100 gl-text-decoration-none! gl-outline-0! gl-display-flex" :class="buttonClass" :title="__('Toggle sidebar')" - data-testid="toggleBtn" @click="toggleDrawer" > - <span v-if="isExpanded" class="gl-text-gray-500 gl-mr-3" data-testid="collapse-text">{{ - __('Collapse') - }}</span> + <span v-if="isExpanded" class="gl-text-gray-500 gl-mr-3" data-testid="collapse-text"> + {{ __('Collapse') }} + </span> <gl-icon data-testid="toggle-icon" :name="buttonIconName" /> </gl-button> - <div v-if="isExpanded" class="gl-p-5" data-testid="drawer-content"></div> + <div v-if="isExpanded" class="gl-h-full gl-p-5" data-testid="drawer-content"> + <getting-started-card class="gl-mb-4" /> + <first-pipeline-card class="gl-mb-4" /> + <visualize-and-lint-card class="gl-mb-4" /> + <pipeline-config-reference-card /> + <div class="gl-h-13"></div> + </div> </aside> </template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue b/app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue new file mode 100644 index 00000000000..049504181c4 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue @@ -0,0 +1,17 @@ +<script> +export default { + props: { + jobName: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div + class="gl-w-13 gl-h-6 gl-font-sm gl-bg-white gl-inset-border-1-blue-500 gl-text-center gl-text-truncate gl-rounded-pill gl-px-4 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" + > + {{ jobName }} + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue b/app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue new file mode 100644 index 00000000000..1017237365b --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue @@ -0,0 +1,43 @@ +<script> +import { s__ } from '~/locale'; +import DemoJobPill from './demo_job_pill.vue'; + +export default { + i18n: { + stageNames: { + build: s__('StageName|Build'), + test: s__('StageName|Test'), + deploy: s__('StageName|Deploy'), + }, + jobNames: { + build: s__('JobName|build-job'), + test_1: s__('JobName|unit-test'), + test_2: s__('JobName|lint-test'), + deploy: s__('JobName|deploy-app'), + }, + }, + stageClasses: + 'gl-bg-blue-50 gl-display-flex gl-flex-direction-column gl-align-items-center gl-p-4 gl-rounded-base', + titleClasses: 'gl-text-blue-600 gl-mb-4', + components: { + DemoJobPill, + }, +}; +</script> +<template> + <div class="gl-display-flex gl-justify-content-center"> + <div :class="$options.stageClasses" class="gl-mr-5"> + <div :class="$options.titleClasses">{{ $options.i18n.stageNames.build }}</div> + <demo-job-pill :job-name="$options.i18n.jobNames.build" /> + </div> + <div :class="$options.stageClasses" class="gl-mr-5"> + <div :class="$options.titleClasses">{{ $options.i18n.stageNames.test }}</div> + <demo-job-pill class="gl-mb-3" :job-name="$options.i18n.jobNames.test_1" /> + <demo-job-pill :job-name="$options.i18n.jobNames.test_2" /> + </div> + <div :class="$options.stageClasses"> + <div :class="$options.titleClasses">{{ $options.i18n.stageNames.deploy }}</div> + <demo-job-pill :job-name="$options.i18n.jobNames.deploy" /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js index 9c5681cf74b..361e2b64e0b 100644 --- a/app/assets/javascripts/pipeline_editor/index.js +++ b/app/assets/javascripts/pipeline_editor/index.js @@ -30,13 +30,19 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { pipelineEtag, // Add to provide/inject API for static values ciConfigPath, + ciExamplesHelpPagePath, + ciHelpPagePath, defaultBranch, emptyStateIllustrationPath, + helpPaths, lintHelpPagePath, + needsHelpPagePath, newMergeRequestPath, + pipelinePagePath, projectFullPath, projectPath, projectNamespace, + runnerHelpPagePath, ymlHelpPagePath, } = el?.dataset; @@ -80,15 +86,21 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { apolloProvider, provide: { ciConfigPath, + ciExamplesHelpPagePath, + ciHelpPagePath, + configurationPaths, defaultBranch, emptyStateIllustrationPath, + helpPaths, lintHelpPagePath, + needsHelpPagePath, newMergeRequestPath, + pipelinePagePath, projectFullPath, projectPath, projectNamespace, + runnerHelpPagePath, ymlHelpPagePath, - configurationPaths, }, render(h) { return h(PipelineEditorApp); diff --git a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue index 0422bfb13c5..80ed9a32039 100644 --- a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue +++ b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue @@ -1,6 +1,6 @@ <script> import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { sprintf, n__ } from '~/locale'; +import { sprintf, n__, s__ } from '~/locale'; import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; @@ -23,6 +23,8 @@ import { ROOT_IMAGE_TOOLTIP, } from '../../constants/index'; +import getContainerRepositoryTagsCountQuery from '../../graphql/queries/get_container_repository_tags_count.query.graphql'; + export default { name: 'DetailsHeader', components: { GlButton, GlIcon, TitleArea, MetadataItem }, @@ -35,60 +37,77 @@ export default { type: Object, required: true, }, - metadataLoading: { - type: Boolean, - required: false, - default: false, - }, disabled: { type: Boolean, default: false, required: false, }, }, + data() { + return { + containerRepository: {}, + fetchTagsCount: false, + }; + }, + apollo: { + containerRepository: { + query: getContainerRepositoryTagsCountQuery, + variables() { + return { + id: this.image.id, + }; + }, + }, + }, computed: { + imageDetails() { + return { ...this.image, ...this.containerRepository }; + }, visibilityIcon() { - return this.image?.project?.visibility === 'public' ? 'eye' : 'eye-slash'; + return this.imageDetails?.project?.visibility === 'public' ? 'eye' : 'eye-slash'; }, timeAgo() { - return this.timeFormatted(this.image.updatedAt); + return this.timeFormatted(this.imageDetails.updatedAt); }, updatedText() { return sprintf(UPDATED_AT, { time: this.timeAgo }); }, tagCountText() { - return n__('%d tag', '%d tags', this.image.tagsCount); + if (this.$apollo.queries.containerRepository.loading) { + return s__('ContainerRegistry|-- tags'); + } + return n__('%d tag', '%d tags', this.imageDetails.tagsCount); }, cleanupTextAndTooltip() { - if (!this.image.project.containerExpirationPolicy?.enabled) { + if (!this.imageDetails.project.containerExpirationPolicy?.enabled) { return { text: CLEANUP_DISABLED_TEXT, tooltip: CLEANUP_DISABLED_TOOLTIP }; } return { [UNSCHEDULED_STATUS]: { text: sprintf(CLEANUP_UNSCHEDULED_TEXT, { - time: this.timeFormatted(this.image.project.containerExpirationPolicy.nextRunAt), + time: this.timeFormatted(this.imageDetails.project.containerExpirationPolicy.nextRunAt), }), }, [SCHEDULED_STATUS]: { text: CLEANUP_SCHEDULED_TEXT, tooltip: CLEANUP_SCHEDULED_TOOLTIP }, [ONGOING_STATUS]: { text: CLEANUP_ONGOING_TEXT, tooltip: CLEANUP_ONGOING_TOOLTIP }, [UNFINISHED_STATUS]: { text: CLEANUP_UNFINISHED_TEXT, tooltip: CLEANUP_UNFINISHED_TOOLTIP }, - }[this.image?.expirationPolicyCleanupStatus]; + }[this.imageDetails?.expirationPolicyCleanupStatus]; }, deleteButtonDisabled() { - return this.disabled || !this.image.canDelete; + return this.disabled || !this.imageDetails.canDelete; }, rootImageTooltip() { - return !this.image.name ? ROOT_IMAGE_TOOLTIP : ''; + return !this.imageDetails.name ? ROOT_IMAGE_TOOLTIP : ''; }, imageName() { - return this.image.name || ROOT_IMAGE_TEXT; + return this.imageDetails.name || ROOT_IMAGE_TEXT; }, }, }; </script> <template> - <title-area :metadata-loading="metadataLoading"> + <title-area> <template #title> <span data-testid="title"> {{ imageName }} @@ -124,12 +143,7 @@ export default { /> </template> <template #right-actions> - <gl-button - v-if="!metadataLoading" - variant="danger" - :disabled="deleteButtonDisabled" - @click="$emit('delete')" - > + <gl-button variant="danger" :disabled="deleteButtonDisabled" @click="$emit('delete')"> {{ __('Delete image repository') }} </gl-button> </template> diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql index 0f50531c3c5..88c2e667afd 100644 --- a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql @@ -8,7 +8,6 @@ query getContainerRepositoryDetails($id: ID!) { canDelete createdAt updatedAt - tagsCount expirationPolicyStartedAt expirationPolicyCleanupStatus project { diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql index c87c0f847b3..a703c2dd0ac 100644 --- a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql @@ -1,6 +1,6 @@ #import "~/graphql_shared/fragments/pageInfo.fragment.graphql" -query getContainerRepositoryDetails( +query getContainerRepositoryTags( $id: ID! $first: Int $last: Int diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql new file mode 100644 index 00000000000..9092a71edb0 --- /dev/null +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql @@ -0,0 +1,6 @@ +query getContainerRepositoryTagsCount($id: ID!) { + containerRepository(id: $id) { + id + tagsCount + } +} diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue index 50feea79747..34ec3b085a5 100644 --- a/app/assets/javascripts/registry/explorer/pages/details.vue +++ b/app/assets/javascripts/registry/explorer/pages/details.vue @@ -48,14 +48,11 @@ export default { mixins: [Tracking.mixin()], inject: ['breadCrumbState', 'config'], apollo: { - image: { + containerRepository: { query: getContainerRepositoryDetailsQuery, variables() { return this.queryVariables; }, - update(data) { - return data.containerRepository; - }, result() { this.updateBreadcrumb(); }, @@ -66,7 +63,7 @@ export default { }, data() { return { - image: {}, + containerRepository: {}, itemsToBeDeleted: [], isMobile: false, mutationLoading: false, @@ -82,12 +79,12 @@ export default { }; }, isLoading() { - return this.$apollo.queries.image.loading || this.mutationLoading; + return this.$apollo.queries.containerRepository.loading || this.mutationLoading; }, showPartialCleanupWarning() { return ( this.config.showUnfinishedTagCleanupCallout && - this.image?.expirationPolicyCleanupStatus === UNFINISHED_STATUS && + this.containerRepository?.expirationPolicyCleanupStatus === UNFINISHED_STATUS && !this.hidePartialCleanupWarning ); }, @@ -98,13 +95,13 @@ export default { }; }, pageActionsAreDisabled() { - return Boolean(this.image?.status); + return Boolean(this.containerRepository?.status); }, }, methods: { updateBreadcrumb() { - const name = this.image?.id - ? this.image?.name || ROOT_IMAGE_TEXT + const name = this.containerRepository?.id + ? this.containerRepository?.name || ROOT_IMAGE_TEXT : MISSING_OR_DELETED_IMAGE_BREADCRUMB; this.breadCrumbState.updateName(name); }, @@ -164,7 +161,7 @@ export default { }, deleteImage() { this.deleteImageAlert = true; - this.itemsToBeDeleted = [{ path: this.image.path }]; + this.itemsToBeDeleted = [{ path: this.containerRepository.path }]; this.$refs.deleteModal.show(); }, deleteImageError() { @@ -180,7 +177,7 @@ export default { <template> <div v-gl-resize-observer="handleResize" class="gl-my-3"> - <template v-if="image"> + <template v-if="containerRepository"> <delete-alert v-model="deleteAlertType" :garbage-collection-help-page-path="config.garbageCollectionHelpPagePath" @@ -195,11 +192,11 @@ export default { @dismiss="dismissPartialCleanupWarning" /> - <status-alert v-if="image.status" :status="image.status" /> + <status-alert v-if="containerRepository.status" :status="containerRepository.status" /> <details-header - :image="image" - :metadata-loading="isLoading" + v-if="!isLoading" + :image="containerRepository" :disabled="pageActionsAreDisabled" @delete="deleteImage" /> @@ -215,7 +212,7 @@ export default { /> <delete-image - :id="image.id" + :id="containerRepository.id" ref="deleteImage" use-update-fn @start="deleteImageIniit" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue index abc831c8abe..a5d165ebd49 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue @@ -1,5 +1,12 @@ <script> -import { GlButtonGroup, GlDropdown, GlDropdownItem, GlLink, GlSearchBoxByType } from '@gitlab/ui'; +import { + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlIcon, + GlLink, + GlSearchBoxByType, +} from '@gitlab/ui'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import ReviewAppLink from '../review_app_link.vue'; @@ -9,6 +16,7 @@ export default { GlButtonGroup, GlDropdown, GlDropdownItem, + GlIcon, GlLink, GlSearchBoxByType, ReviewAppLink, @@ -71,7 +79,14 @@ export default { size="small" css-class="deploy-link js-deploy-url inline" /> - <gl-dropdown size="small" class="js-mr-wigdet-deployment-dropdown"> + <gl-dropdown toggle-class="gl-px-2!" size="small" class="js-mr-wigdet-deployment-dropdown"> + <template #button-content> + <gl-icon + class="dropdown-chevron gl-mx-0!" + name="chevron-down" + data-testid="mr-wigdet-deployment-dropdown-icon" + /> + </template> <gl-search-box-by-type v-model.trim="searchTerm" v-autofocusonshow autofocus /> <gl-dropdown-item v-for="change in filteredChanges" diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 1504f3ee50f..9b38e842635 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -50,6 +50,12 @@ img.avatar { margin-right: $gl-padding; + + @include media-breakpoint-down(sm) { + width: $gl-spacing-scale-6; + height: $gl-spacing-scale-6; + margin-right: $gl-padding-8; + } } .controls { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 801dd44be8e..c2ed709a475 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -676,6 +676,7 @@ $system-note-svg-size: 16px; @include notes-media('max', map-get($grid-breakpoints, sm) - 1) { float: none; margin-left: 0; + transform: translateY(-4px); } } diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb index 8c2b0300589..d4bff730b06 100644 --- a/app/controllers/concerns/boards_actions.rb +++ b/app/controllers/concerns/boards_actions.rb @@ -19,7 +19,7 @@ module BoardsActions def show # Add / update the board in the recent visits table - Boards::Visits::CreateService.new(parent, current_user).execute(board) if request.format.html? + board_visit_service.new(parent, current_user).execute(board) if request.format.html? respond_with_board end @@ -52,6 +52,10 @@ module BoardsActions board_klass.to_type end + def board_visit_service + Boards::Visits::CreateService + end + def serializer BoardSerializer.new(current_user: current_user) end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 64b7cbfacc3..43f1a1a847d 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -4,6 +4,7 @@ class Groups::GroupMembersController < Groups::ApplicationController include MembershipActions include MembersPresentation include SortingHelper + include Gitlab::Utils::StrongMemoize MEMBER_PER_PAGE_LIMIT = 50 @@ -21,6 +22,8 @@ class Groups::GroupMembersController < Groups::ApplicationController feature_category :authentication_and_authorization + helper_method :can_manage_members? + def index preload_max_access @sort = params[:sort].presence || sort_value_name @@ -29,7 +32,7 @@ class Groups::GroupMembersController < Groups::ApplicationController .new(@group, current_user, params: filter_params) .execute(include_relations: requested_relations) - if can_manage_members + if can_manage_members? @skip_groups = @group.related_group_ids @invited_members = @members.invite @@ -59,8 +62,10 @@ class Groups::GroupMembersController < Groups::ApplicationController current_user.max_access_for_group[@group.id] = @group.max_member_access(current_user) end - def can_manage_members - can?(current_user, :admin_group_member, @group) + def can_manage_members? + strong_memoize(:can_manage_members) do + can?(current_user, :admin_group_member, @group) + end end def present_invited_members(invited_members) diff --git a/app/finders/concerns/packages/finder_helper.rb b/app/finders/concerns/packages/finder_helper.rb index 6ba353fd89a..061e289d8d8 100644 --- a/app/finders/concerns/packages/finder_helper.rb +++ b/app/finders/concerns/packages/finder_helper.rb @@ -9,12 +9,16 @@ module Packages private + def packages_for_project(project) + project.packages.installable + end + def packages_visible_to_user(user, within_group:) return ::Packages::Package.none unless within_group return ::Packages::Package.none unless Ability.allowed?(user, :read_group, within_group) projects = projects_visible_to_reporters(user, within_group: within_group) - ::Packages::Package.for_projects(projects.select(:id)) + ::Packages::Package.for_projects(projects.select(:id)).installable end def projects_visible_to_user(user, within_group:) diff --git a/app/finders/packages/composer/packages_finder.rb b/app/finders/packages/composer/packages_finder.rb index e63b2ee03fa..b5a1b19216f 100644 --- a/app/finders/packages/composer/packages_finder.rb +++ b/app/finders/packages/composer/packages_finder.rb @@ -9,7 +9,7 @@ module Packages end def execute - packages_for_group_projects.composer.preload_composer + packages_for_group_projects(installable_only: true).composer.preload_composer end end end diff --git a/app/finders/packages/conan/package_finder.rb b/app/finders/packages/conan/package_finder.rb index 26e9182f4e1..8ebdd358ba6 100644 --- a/app/finders/packages/conan/package_finder.rb +++ b/app/finders/packages/conan/package_finder.rb @@ -11,7 +11,7 @@ module Packages end def execute - packages_for_current_user.with_name_like(query).order_name_asc if query + packages_for_current_user.installable.with_name_like(query).order_name_asc if query end private diff --git a/app/finders/packages/generic/package_finder.rb b/app/finders/packages/generic/package_finder.rb index 3a260e11fa3..8ec88754901 100644 --- a/app/finders/packages/generic/package_finder.rb +++ b/app/finders/packages/generic/package_finder.rb @@ -11,6 +11,7 @@ module Packages project .packages .generic + .installable .by_name_and_version!(package_name, package_version) end diff --git a/app/finders/packages/go/package_finder.rb b/app/finders/packages/go/package_finder.rb index 4573417d11f..553e731895d 100644 --- a/app/finders/packages/go/package_finder.rb +++ b/app/finders/packages/go/package_finder.rb @@ -21,6 +21,7 @@ module Packages @project .packages .golang + .installable .with_name(@module_name) .with_version(@module_version) end diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb index 8771bf90e75..ab12a580e30 100644 --- a/app/finders/packages/group_packages_finder.rb +++ b/app/finders/packages/group_packages_finder.rb @@ -20,7 +20,7 @@ module Packages attr_reader :current_user, :group, :params - def packages_for_group_projects + def packages_for_group_projects(installable_only: false) packages = ::Packages::Package .including_build_info .including_project_route @@ -32,7 +32,7 @@ module Packages packages = filter_with_version(packages) packages = filter_by_package_type(packages) packages = filter_by_package_name(packages) - filter_by_status(packages) + installable_only ? packages.installable : filter_by_status(packages) end def group_projects_visible_to_current_user diff --git a/app/finders/packages/maven/package_finder.rb b/app/finders/packages/maven/package_finder.rb index 3bc2f66c098..fd5444684c6 100644 --- a/app/finders/packages/maven/package_finder.rb +++ b/app/finders/packages/maven/package_finder.rb @@ -26,9 +26,9 @@ module Packages def base if @project - packages_for_a_single_project + packages_for_project(@project) elsif @group - packages_for_multiple_projects + packages_visible_to_user(@current_user, within_group: @group) else ::Packages::Package.none end @@ -40,23 +40,6 @@ module Packages matching_packages end - - # Produces a query that retrieves packages from a single project. - def packages_for_a_single_project - @project.packages - end - - # Produces a query that retrieves packages from multiple projects that - # the current user can view within a group. - def packages_for_multiple_projects - packages_visible_to_user(@current_user, within_group: @group) - end - - # Returns the projects that the current user can view within a group. - def projects_visible_to_current_user - @group.all_projects - .public_or_visible_to_user(@current_user) - end end end end diff --git a/app/finders/packages/npm/package_finder.rb b/app/finders/packages/npm/package_finder.rb index 3b79785d0e1..92ceac297ee 100644 --- a/app/finders/packages/npm/package_finder.rb +++ b/app/finders/packages/npm/package_finder.rb @@ -14,6 +14,7 @@ module Packages def execute base.npm .with_name(@package_name) + .installable .last_of_each_version .preload_files end diff --git a/app/finders/packages/nuget/package_finder.rb b/app/finders/packages/nuget/package_finder.rb index 2f66bd145ee..d91ef853a1a 100644 --- a/app/finders/packages/nuget/package_finder.rb +++ b/app/finders/packages/nuget/package_finder.rb @@ -23,7 +23,7 @@ module Packages def base if project? - @project_or_group.packages + packages_for_project(@project_or_group) elsif group? packages_visible_to_user(@current_user, within_group: @project_or_group) else diff --git a/app/finders/packages/package_finder.rb b/app/finders/packages/package_finder.rb index f1874b77845..368d2028cb5 100644 --- a/app/finders/packages/package_finder.rb +++ b/app/finders/packages/package_finder.rb @@ -12,6 +12,7 @@ module Packages .including_build_info .including_project_route .including_tags + .displayable .processed .find(@package_id) end diff --git a/app/graphql/queries/epic/epic_children.query.graphql b/app/graphql/queries/epic/epic_children.query.graphql index c12778109d0..5ee27052f95 100644 --- a/app/graphql/queries/epic/epic_children.query.graphql +++ b/app/graphql/queries/epic/epic_children.query.graphql @@ -16,6 +16,10 @@ fragment RelatedTreeBaseEpic on Epic { adminEpic createEpic } + descendantWeightSum { + closedIssues + openedIssues + } descendantCounts { __typename openedEpics diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb index 193cf49d27e..c31f7843e40 100644 --- a/app/helpers/ci/pipeline_editor_helper.rb +++ b/app/helpers/ci/pipeline_editor_helper.rb @@ -12,16 +12,21 @@ module Ci commit_sha = project.commit ? project.commit.sha : '' { "ci-config-path": project.ci_config_path_or_default, + "ci-examples-help-page-path" => help_page_path('ci/examples/README'), + "ci-help-page-path" => help_page_path('ci/README'), "commit-sha" => commit_sha, "default-branch" => project.default_branch, "empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'), "initial-branch-name": params[:branch_name], "lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'), + "needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'), "new-merge-request-path" => namespace_project_new_merge_request_path, "pipeline_etag" => project.commit ? graphql_etag_pipeline_sha_path(commit_sha) : '', + "pipeline-page-path" => project_pipelines_path(project), "project-path" => project.path, "project-full-path" => project.full_path, "project-namespace" => project.namespace.full_path, + "runner-help-page-path" => help_page_path('ci/runners/README'), "yml-help-page-path" => help_page_path('ci/yaml/README') } end diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb index 979f0e1ab92..dc273e256a8 100644 --- a/app/models/board_group_recent_visit.rb +++ b/app/models/board_group_recent_visit.rb @@ -2,27 +2,19 @@ # Tracks which boards in a specific group a user has visited class BoardGroupRecentVisit < ApplicationRecord + include BoardRecentVisit + belongs_to :user belongs_to :group belongs_to :board - validates :user, presence: true + validates :user, presence: true validates :group, presence: true validates :board, presence: true - scope :by_user_group, -> (user, group) { where(user: user, group: group) } - - def self.visited!(user, board) - visit = find_or_create_by(user: user, group: board.group, board: board) - visit.touch if visit.updated_at < Time.current - rescue ActiveRecord::RecordNotUnique - retry - end - - def self.latest(user, group, count: nil) - visits = by_user_group(user, group).order(updated_at: :desc) - visits = visits.preload(:board) if count && count > 1 + scope :by_user_parent, -> (user, group) { where(user: user, group: group) } - visits.first(count) + def self.board_parent_relation + :group end end diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb index 509c8f97b83..723afd6feab 100644 --- a/app/models/board_project_recent_visit.rb +++ b/app/models/board_project_recent_visit.rb @@ -2,27 +2,19 @@ # Tracks which boards in a specific project a user has visited class BoardProjectRecentVisit < ApplicationRecord + include BoardRecentVisit + belongs_to :user belongs_to :project belongs_to :board - validates :user, presence: true + validates :user, presence: true validates :project, presence: true validates :board, presence: true - scope :by_user_project, -> (user, project) { where(user: user, project: project) } - - def self.visited!(user, board) - visit = find_or_create_by(user: user, project: board.project, board: board) - visit.touch if visit.updated_at < Time.current - rescue ActiveRecord::RecordNotUnique - retry - end - - def self.latest(user, project, count: nil) - visits = by_user_project(user, project).order(updated_at: :desc) - visits = visits.preload(:board) if count && count > 1 + scope :by_user_parent, -> (user, project) { where(user: user, project: project) } - visits.first(count) + def self.board_parent_relation + :project end end diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb index 04af1145769..bb543b39a79 100644 --- a/app/models/bulk_imports/entity.rb +++ b/app/models/bulk_imports/entity.rb @@ -68,6 +68,10 @@ class BulkImports::Entity < ApplicationRecord end end + def encoded_source_full_path + ERB::Util.url_encode(source_full_path) + end + private def validate_parent_is_a_group diff --git a/app/models/concerns/board_recent_visit.rb b/app/models/concerns/board_recent_visit.rb new file mode 100644 index 00000000000..fd4d574ac58 --- /dev/null +++ b/app/models/concerns/board_recent_visit.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module BoardRecentVisit + extend ActiveSupport::Concern + + class_methods do + def visited!(user, board) + find_or_create_by( + "user" => user, + board_parent_relation => board.resource_parent, + board_relation => board + ).tap do |visit| + visit.touch + end + rescue ActiveRecord::RecordNotUnique + retry + end + + def latest(user, parent, count: nil) + visits = by_user_parent(user, parent).order(updated_at: :desc) + visits = visits.preload(board_relation) + + visits.first(count) + end + + def board_relation + :board + end + + def board_parent_relation + raise NotImplementedError + end + end +end diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index ed620c5d6c3..bfa27132299 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -6,6 +6,7 @@ class Packages::Package < ApplicationRecord include Gitlab::Utils::StrongMemoize DISPLAYABLE_STATUSES = [:default, :error].freeze + INSTALLABLE_STATUSES = [:default].freeze belongs_to :project belongs_to :creator, class_name: 'User' @@ -86,6 +87,7 @@ class Packages::Package < ApplicationRecord scope :with_package_type, ->(package_type) { where(package_type: package_type) } scope :with_status, ->(status) { where(status: status) } scope :displayable, -> { with_status(DISPLAYABLE_STATUSES) } + scope :installable, -> { with_status(INSTALLABLE_STATUSES) } scope :including_build_info, -> { includes(pipelines: :user) } scope :including_project_route, -> { includes(project: { namespace: :route }) } scope :including_tags, -> { includes(:tags) } diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 5c4b1564914..f1db78501d5 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -106,9 +106,8 @@ class JiraService < IssueTrackerService end def help - "You need to configure Jira before enabling this service. For more details - read the - [Jira service documentation](#{help_page_url('user/project/integrations/jira')})." + jira_doc_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_url('integration/jira/index.html') } + s_("JiraService|You need to configure Jira before enabling this integration. For more details, read the %{jira_doc_link_start}Jira integration documentation%{link_end}.") % { jira_doc_link_start: jira_doc_link_start, link_end: '</a>'.html_safe } end def title diff --git a/app/services/boards/visits/create_service.rb b/app/services/boards/visits/create_service.rb index 428ed1a8bcc..4d659596803 100644 --- a/app/services/boards/visits/create_service.rb +++ b/app/services/boards/visits/create_service.rb @@ -5,13 +5,17 @@ module Boards class CreateService < Boards::BaseService def execute(board) return unless current_user && Gitlab::Database.read_write? - return unless board.is_a?(Board) # other board types do not support board visits yet + return unless board - if parent.is_a?(Group) - BoardGroupRecentVisit.visited!(current_user, board) - else - BoardProjectRecentVisit.visited!(current_user, board) - end + model.visited!(current_user, board) + end + + private + + def model + return BoardGroupRecentVisit if parent.is_a?(Group) + + BoardProjectRecentVisit end end end diff --git a/app/services/packages/nuget/search_service.rb b/app/services/packages/nuget/search_service.rb index 1eead1e62b3..fea424b3aa8 100644 --- a/app/services/packages/nuget/search_service.rb +++ b/app/services/packages/nuget/search_service.rb @@ -103,6 +103,7 @@ module Packages def nuget_packages Packages::Package.nuget + .displayable .has_version .without_nuget_temporary_name end diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index cea4c533ae3..5da3a94c44b 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,15 +1,14 @@ - add_page_specific_style 'page_bundles/members' - page_title _('Group members') -- can_manage_members = can?(current_user, :admin_group_member, @group) -- show_invited_members = can_manage_members && @invited_members.exists? -- show_access_requests = can_manage_members && @requesters.exists? +- show_invited_members = can_manage_members? && @invited_members.load.any? +- show_access_requests = can_manage_members? && @requesters.load.any? - invited_active = params[:search_invited].present? || params[:invited_members_page].present? .js-remove-member-modal .row.gl-mt-3 .col-lg-12 .gl-display-flex.gl-flex-wrap - - if can_manage_members + - if can_manage_members? .gl-w-half.gl-xs-w-full %h4 = _('Group members') @@ -21,7 +20,7 @@ .js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } } .js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } } = render 'groups/invite_members_modal', group: @group - - if can_manage_members && Feature.disabled?(:invite_members_group_modal, @group) + - if can_manage_members? && Feature.disabled?(:invite_members_group_modal, @group) %hr.gl-mt-4 %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' } %li.nav-tab{ role: 'presentation' } @@ -42,7 +41,7 @@ %span = _('Members') %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @members.total_count - - if @group.shared_with_group_links.any? + - if @group.shared_with_group_links.present? %li.nav-item = link_to '#tab-groups', class: ['nav-link'] , data: { toggle: 'tab', qa_selector: 'groups_list_tab' } do %span @@ -65,7 +64,7 @@ .js-group-members-list{ data: group_members_list_data_attributes(@group, @members, { param_name: :page, params: { invited_members_page: nil, search_invited: nil } }) } .loading .gl-spinner.gl-spinner-md - - if @group.shared_with_group_links.any? + - if @group.shared_with_group_links.present? #tab-groups.tab-pane .js-group-group-links-list{ data: group_group_links_list_data_attributes(@group) } .loading diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 40ff568c80b..7d435ac03f8 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1836,6 +1836,15 @@ :idempotent: :tags: - :exclude_from_kubernetes +- :name: bulk_imports_export_request + :worker_name: BulkImports::ExportRequestWorker + :feature_category: :importers + :has_external_dependencies: true + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: bulk_imports_pipeline :worker_name: BulkImports::PipelineWorker :feature_category: :importers diff --git a/app/workers/bulk_import_worker.rb b/app/workers/bulk_import_worker.rb index f6b1aef5069..8ad31c68374 100644 --- a/app/workers/bulk_import_worker.rb +++ b/app/workers/bulk_import_worker.rb @@ -24,6 +24,7 @@ class BulkImportWorker # rubocop:disable Scalability/IdempotentWorker created_entities.first(next_batch_size).each do |entity| create_pipeline_tracker_for(entity) + BulkImports::ExportRequestWorker.perform_async(entity.id) BulkImports::EntityWorker.perform_async(entity.id) entity.start! diff --git a/app/workers/bulk_imports/export_request_worker.rb b/app/workers/bulk_imports/export_request_worker.rb new file mode 100644 index 00000000000..cccc24d3bdc --- /dev/null +++ b/app/workers/bulk_imports/export_request_worker.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module BulkImports + class ExportRequestWorker + include ApplicationWorker + + idempotent! + worker_has_external_dependencies! + feature_category :importers + + GROUP_EXPORTED_URL_PATH = "/groups/%s/export_relations" + + def perform(entity_id) + entity = BulkImports::Entity.find(entity_id) + + request_export(entity) + end + + private + + def request_export(entity) + http_client(entity.bulk_import.configuration) + .post(GROUP_EXPORTED_URL_PATH % entity.encoded_source_full_path) + end + + def http_client(configuration) + @client ||= Clients::Http.new( + uri: configuration.url, + token: configuration.access_token + ) + end + end +end |