Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-11 18:10:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-11 18:10:20 +0300
commite3042fc5ced749e693ccef81b3f5838c55d5480c (patch)
treee004dca26da0ec413d5c9ebf174962a008fde0bb /app
parentc33a9adb709ffb40f816e66eb0c98cc750d6cd43 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/emoji/components/picker.vue1
-rw-r--r--app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue6
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue9
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue1
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue67
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue35
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue75
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue24
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue25
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue17
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue43
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js14
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/details_header.vue58
-rw-r--r--app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql1
-rw-r--r--app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql2
-rw-r--r--app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql6
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue29
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue19
-rw-r--r--app/assets/stylesheets/framework/timeline.scss6
-rw-r--r--app/assets/stylesheets/pages/notes.scss1
-rw-r--r--app/controllers/concerns/boards_actions.rb6
-rw-r--r--app/controllers/groups/group_members_controller.rb11
-rw-r--r--app/finders/concerns/packages/finder_helper.rb6
-rw-r--r--app/finders/packages/composer/packages_finder.rb2
-rw-r--r--app/finders/packages/conan/package_finder.rb2
-rw-r--r--app/finders/packages/generic/package_finder.rb1
-rw-r--r--app/finders/packages/go/package_finder.rb1
-rw-r--r--app/finders/packages/group_packages_finder.rb4
-rw-r--r--app/finders/packages/maven/package_finder.rb21
-rw-r--r--app/finders/packages/npm/package_finder.rb1
-rw-r--r--app/finders/packages/nuget/package_finder.rb2
-rw-r--r--app/finders/packages/package_finder.rb1
-rw-r--r--app/graphql/queries/epic/epic_children.query.graphql4
-rw-r--r--app/helpers/ci/pipeline_editor_helper.rb5
-rw-r--r--app/models/board_group_recent_visit.rb20
-rw-r--r--app/models/board_project_recent_visit.rb20
-rw-r--r--app/models/bulk_imports/entity.rb4
-rw-r--r--app/models/concerns/board_recent_visit.rb34
-rw-r--r--app/models/packages/package.rb2
-rw-r--r--app/models/project_services/jira_service.rb5
-rw-r--r--app/services/boards/visits/create_service.rb16
-rw-r--r--app/services/packages/nuget/search_service.rb1
-rw-r--r--app/views/groups/group_members/index.html.haml13
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/bulk_import_worker.rb1
-rw-r--r--app/workers/bulk_imports/export_request_worker.rb33
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