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-03-03 21:11:16 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-03 21:11:16 +0300
commit9578c9f9e88421a5dc4d9215f40d932bd30cbabc (patch)
tree51cc56403430f901de45cb82a6ab5f63c1f37712 /app
parent7fcda12793acc54ba8de037f50cc3696dbd0f002 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/design_management/components/delete_button.vue16
-rw-r--r--app/assets/javascripts/design_management/components/design_destroyer.vue1
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/index.vue2
-rw-r--r--app/assets/javascripts/design_management/components/upload/button.vue2
-rw-r--r--app/assets/javascripts/design_management/pages/index.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue9
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/graph/utils.js21
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue119
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue85
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue37
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue155
-rw-r--r--app/assets/javascripts/pipelines/constants.js2
-rw-r--r--app/assets/javascripts/ref/components/ref_results_section.vue8
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue58
-rw-r--r--app/assets/javascripts/ref/constants.js5
-rw-r--r--app/assets/javascripts/ref/stores/actions.js17
-rw-r--r--app/assets/javascripts/ref/stores/mutation_types.js2
-rw-r--r--app/assets/javascripts/ref/stores/mutations.js3
-rw-r--r--app/assets/javascripts/ref/stores/state.js25
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/root_controller.rb2
-rw-r--r--app/helpers/preferences_helper.rb1
-rw-r--r--app/models/user.rb2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml6
27 files changed, 535 insertions, 60 deletions
diff --git a/app/assets/javascripts/design_management/components/delete_button.vue b/app/assets/javascripts/design_management/components/delete_button.vue
index 273fa3f6be2..fbcce22ec1e 100644
--- a/app/assets/javascripts/design_management/components/delete_button.vue
+++ b/app/assets/javascripts/design_management/components/delete_button.vue
@@ -73,21 +73,19 @@ export default {
</script>
<template>
- <div class="gl-display-flex gl-align-items-center gl-h-full">
+ <div>
<gl-modal
:modal-id="modalId"
:title="$options.modal.title"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
- @ok="$emit('deleteSelectedDesigns')"
+ @ok="$emit('delete-selected-designs')"
>
- <p>
- {{
- s__(
- 'DesignManagement|Archived designs will still be available in previous versions of the design collection.',
- )
- }}
- </p>
+ {{
+ s__(
+ 'DesignManagement|Archived designs will still be available in previous versions of the design collection.',
+ )
+ }}
</gl-modal>
<gl-button
v-gl-modal-directive="modalId"
diff --git a/app/assets/javascripts/design_management/components/design_destroyer.vue b/app/assets/javascripts/design_management/components/design_destroyer.vue
index 01f9cac456d..0178111f651 100644
--- a/app/assets/javascripts/design_management/components/design_destroyer.vue
+++ b/app/assets/javascripts/design_management/components/design_destroyer.vue
@@ -55,6 +55,7 @@ export default {
iid,
}"
:update="updateStoreAfterDelete"
+ :tag="null"
v-on="$listeners"
>
<slot v-bind="{ mutate, loading, error }"></slot>
diff --git a/app/assets/javascripts/design_management/components/toolbar/index.vue b/app/assets/javascripts/design_management/components/toolbar/index.vue
index a3b0f06fb28..8abf1529f3c 100644
--- a/app/assets/javascripts/design_management/components/toolbar/index.vue
+++ b/app/assets/javascripts/design_management/components/toolbar/index.vue
@@ -130,7 +130,7 @@ export default {
button-icon="archive"
button-category="secondary"
:title="s__('DesignManagement|Archive design')"
- @deleteSelectedDesigns="$emit('delete')"
+ @delete-selected-designs="$emit('delete')"
/>
</header>
</template>
diff --git a/app/assets/javascripts/design_management/components/upload/button.vue b/app/assets/javascripts/design_management/components/upload/button.vue
index d7b287f663b..394ccb3c483 100644
--- a/app/assets/javascripts/design_management/components/upload/button.vue
+++ b/app/assets/javascripts/design_management/components/upload/button.vue
@@ -50,7 +50,7 @@ export default {
type="file"
name="design_file"
:accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype"
- class="hide"
+ class="gl-display-none"
multiple
@change="onFileUploadChange"
/>
diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue
index c73c8fb6ca4..99ac38fc554 100644
--- a/app/assets/javascripts/design_management/pages/index.vue
+++ b/app/assets/javascripts/design_management/pages/index.vue
@@ -365,7 +365,8 @@ export default {
v-if="isLatestVersion"
variant="link"
size="small"
- class="gl-mr-4 js-select-all"
+ class="gl-mr-3"
+ data-testid="select-all-designs-button"
@click="toggleDesignsSelection"
>{{ selectAllButtonText }}
</gl-button>
@@ -385,7 +386,7 @@ export default {
data-qa-selector="archive_button"
:loading="loading"
:has-selected-designs="hasSelectedDesigns"
- @deleteSelectedDesigns="mutate()"
+ @delete-selected-designs="mutate()"
>
{{ s__('DesignManagement|Archive selected') }}
</delete-button>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index 9c922f0ec13..fa0f959bce9 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -7,6 +7,7 @@ import PipelineGraph from './graph_component.vue';
import {
getQueryHeaders,
reportToSentry,
+ serializeGqlErr,
toggleQueryPollingByVisibility,
unwrapPipelineData,
} from './utils';
@@ -60,8 +61,8 @@ export default {
update(data) {
return unwrapPipelineData(this.pipelineProjectPath, data);
},
- error() {
- this.reportFailure(LOAD_FAILURE);
+ error({ gqlError }) {
+ this.reportFailure(LOAD_FAILURE, serializeGqlErr(gqlError));
},
},
},
@@ -112,10 +113,10 @@ export default {
refreshPipelineGraph() {
this.$apollo.queries.pipeline.refetch();
},
- reportFailure(type) {
+ reportFailure(type, err = '') {
this.showAlert = true;
this.alertType = type;
- reportToSentry(this.$options.name, this.alertType);
+ reportToSentry(this.$options.name, `type: ${this.alertType}, info: ${err}`);
},
},
};
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index 356207cbdd4..cccabf8619b 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -6,6 +6,7 @@ import LinkedPipeline from './linked_pipeline.vue';
import {
getQueryHeaders,
reportToSentry,
+ serializeGqlErr,
toggleQueryPollingByVisibility,
unwrapPipelineData,
validateConfigPaths,
@@ -99,12 +100,14 @@ export default {
this.loadingPipelineId = null;
this.$emit('scrollContainer');
},
- error(err, _vm, _key, type) {
+ error({ gqlError }, _vm, _key, type) {
this.$emit('error', LOAD_FAILURE);
reportToSentry(
'linked_pipelines_column',
- `error type: ${LOAD_FAILURE}, error: ${err}, apollo error type: ${type}`,
+ `error type: ${LOAD_FAILURE}, error: ${serializeGqlErr(
+ gqlError,
+ )}, apollo error type: ${type}`,
);
},
});
diff --git a/app/assets/javascripts/pipelines/components/graph/utils.js b/app/assets/javascripts/pipelines/components/graph/utils.js
index 9e4936c1c24..81d8fe7f489 100644
--- a/app/assets/javascripts/pipelines/components/graph/utils.js
+++ b/app/assets/javascripts/pipelines/components/graph/utils.js
@@ -23,7 +23,6 @@ const getQueryHeaders = (etagResource) => {
},
};
};
-/* eslint-enable @gitlab/require-i18n-strings */
const reportToSentry = (component, failureType) => {
Sentry.withScope((scope) => {
@@ -32,6 +31,25 @@ const reportToSentry = (component, failureType) => {
});
};
+const serializeGqlErr = (gqlError) => {
+ if (!gqlError) {
+ return 'gqlError data not available.';
+ }
+
+ const { locations, message, path } = gqlError;
+
+ return `
+ ${message}.
+ Locations: ${locations
+ .flatMap((loc) => Object.entries(loc))
+ .flat(2)
+ .join(' ')}.
+ Path: ${path.join(', ')}.
+ `;
+};
+
+/* eslint-enable @gitlab/require-i18n-strings */
+
const toggleQueryPollingByVisibility = (queryRef, interval = 10000) => {
const stopStartQuery = (query) => {
if (!Visibility.hidden()) {
@@ -82,6 +100,7 @@ const validateConfigPaths = (value) => value.graphqlResourceEtag?.length > 0;
export {
getQueryHeaders,
reportToSentry,
+ serializeGqlErr,
toggleQueryPollingByVisibility,
unwrapPipelineData,
validateConfigPaths,
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
new file mode 100644
index 00000000000..81eeead2171
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
@@ -0,0 +1,119 @@
+<script>
+import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import eventHub from '../../event_hub';
+import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
+import PipelinesManualActions from './pipelines_manual_actions.vue';
+
+export default {
+ i18n: {
+ cancelTitle: __('Cancel'),
+ redeployTitle: __('Retry'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlModalDirective,
+ },
+ components: {
+ GlButton,
+ PipelinesManualActions,
+ PipelinesArtifactsComponent,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ cancelingPipeline: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ isRetrying: false,
+ };
+ },
+ computed: {
+ displayPipelineActions() {
+ return (
+ this.pipeline.flags.retryable ||
+ this.pipeline.flags.cancelable ||
+ this.pipeline.details.manual_actions.length ||
+ this.pipeline.details.artifacts.length
+ );
+ },
+ actions() {
+ if (!this.pipeline || !this.pipeline.details) {
+ return [];
+ }
+ const { details } = this.pipeline;
+ return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
+ },
+ isCancelling() {
+ return this.cancelingPipeline === this.pipeline.id;
+ },
+ },
+ watch: {
+ pipeline() {
+ this.isRetrying = false;
+ },
+ },
+ methods: {
+ handleCancelClick() {
+ eventHub.$emit('openConfirmationModal', {
+ pipeline: this.pipeline,
+ endpoint: this.pipeline.cancel_path,
+ });
+ },
+ handleRetryClick() {
+ this.isRetrying = true;
+ eventHub.$emit('retryPipeline', this.pipeline.retry_path);
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="displayPipelineActions" class="gl-text-right">
+ <div class="btn-group">
+ <pipelines-manual-actions v-if="actions.length > 0" :actions="actions" />
+
+ <pipelines-artifacts-component
+ v-if="pipeline.details.artifacts.length"
+ :artifacts="pipeline.details.artifacts"
+ />
+
+ <gl-button
+ v-if="pipeline.flags.retryable"
+ v-gl-tooltip.hover
+ :aria-label="$options.i18n.redeployTitle"
+ :title="$options.i18n.redeployTitle"
+ :disabled="isRetrying"
+ :loading="isRetrying"
+ class="js-pipelines-retry-button"
+ data-qa-selector="pipeline_retry_button"
+ icon="repeat"
+ variant="default"
+ category="secondary"
+ @click="handleRetryClick"
+ />
+
+ <gl-button
+ v-if="pipeline.flags.cancelable"
+ v-gl-tooltip.hover
+ v-gl-modal-directive="'confirmation-modal'"
+ :aria-label="$options.i18n.cancelTitle"
+ :title="$options.i18n.cancelTitle"
+ :loading="isCancelling"
+ :disabled="isCancelling"
+ icon="close"
+ variant="danger"
+ category="primary"
+ class="js-pipelines-cancel-button"
+ @click="handleCancelClick"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
index 6955b27cb22..c707b395192 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -29,7 +29,7 @@ export default {
};
</script>
<template>
- <div :class="classes">
+ <div :class="classes" data-testid="pipeline-triggerer">
<user-avatar-link
v-if="user"
:link-href="user.path"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index 2a8a0279e62..0de520a2ca7 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -61,7 +61,7 @@ export default {
};
</script>
<template>
- <div :class="classes">
+ <div :class="classes" data-testid="pipeline-url-table-cell">
<gl-link
:href="pipeline.path"
data-testid="pipeline-url-link"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue
new file mode 100644
index 00000000000..cc676883c1d
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue
@@ -0,0 +1,85 @@
+<script>
+import { CHILD_VIEW } from '~/pipelines/constants';
+import CommitComponent from '~/vue_shared/components/commit.vue';
+
+export default {
+ components: {
+ CommitComponent,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ commitAuthor() {
+ let commitAuthorInformation;
+
+ if (!this.pipeline || !this.pipeline.commit) {
+ return null;
+ }
+
+ // 1. person who is an author of a commit might be a GitLab user
+ if (this.pipeline.commit.author) {
+ // 2. if person who is an author of a commit is a GitLab user
+ // they can have a GitLab avatar
+ if (this.pipeline.commit.author.avatar_url) {
+ commitAuthorInformation = this.pipeline.commit.author;
+
+ // 3. If GitLab user does not have avatar, they might have a Gravatar
+ } else if (this.pipeline.commit.author_gravatar_url) {
+ commitAuthorInformation = {
+ ...this.pipeline.commit.author,
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ };
+ }
+ // 4. If committer is not a GitLab User, they can have a Gravatar
+ } else {
+ commitAuthorInformation = {
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ path: `mailto:${this.pipeline.commit.author_email}`,
+ username: this.pipeline.commit.author_name,
+ };
+ }
+
+ return commitAuthorInformation;
+ },
+ commitTag() {
+ return this.pipeline?.ref?.tag;
+ },
+ commitRef() {
+ return this.pipeline?.ref;
+ },
+ commitUrl() {
+ return this.pipeline?.commit?.commit_path;
+ },
+ commitShortSha() {
+ return this.pipeline?.commit?.short_id;
+ },
+ commitTitle() {
+ return this.pipeline?.commit?.title;
+ },
+ isChildView() {
+ return this.viewType === CHILD_VIEW;
+ },
+ },
+};
+</script>
+
+<template>
+ <commit-component
+ :tag="commitTag"
+ :commit-ref="commitRef"
+ :commit-url="commitUrl"
+ :merge-request-ref="pipeline.merge_request"
+ :short-sha="commitShortSha"
+ :title="commitTitle"
+ :author="commitAuthor"
+ :show-ref-info="!isChildView"
+ />
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
new file mode 100644
index 00000000000..cc3c8d522b3
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
@@ -0,0 +1,37 @@
+<script>
+import { CHILD_VIEW } from '~/pipelines/constants';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+
+export default {
+ components: {
+ CiBadge,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ pipelineStatus() {
+ return this.pipeline?.details?.status ?? {};
+ },
+ isChildView() {
+ return this.viewType === CHILD_VIEW;
+ },
+ },
+};
+</script>
+
+<template>
+ <ci-badge
+ :status="pipelineStatus"
+ :show-text="!isChildView"
+ :icon-classes="'gl-vertical-align-middle!'"
+ data-qa-selector="pipeline_commit_status"
+ />
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index 5894101ebc6..5747287d2ad 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -1,15 +1,93 @@
<script>
import { GlTable, GlTooltipDirective } from '@gitlab/ui';
+import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
+import PipelineOperations from './pipeline_operations.vue';
import PipelineStopModal from './pipeline_stop_modal.vue';
+import PipelineTriggerer from './pipeline_triggerer.vue';
+import PipelineUrl from './pipeline_url.vue';
+import PipelinesCommit from './pipelines_commit.vue';
+import PipelinesStatusBadge from './pipelines_status_badge.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
+import PipelineStage from './stage.vue';
+import PipelinesTimeago from './time_ago.vue';
+
+const DEFAULT_TD_CLASS = 'gl-p-5!';
+const HIDE_TD_ON_MOBILE = 'gl-display-none! gl-lg-display-table-cell!';
+const DEFAULT_TH_CLASSES =
+ 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1! gl-font-sm!';
export default {
+ fields: [
+ {
+ key: 'status',
+ label: s__('Pipeline|Status'),
+ thClass: DEFAULT_TH_CLASSES,
+ columnClass: 'gl-w-10p',
+ tdClass: DEFAULT_TD_CLASS,
+ thAttr: { 'data-testid': 'status-th' },
+ },
+ {
+ key: 'pipeline',
+ label: s__('Pipeline|Pipeline'),
+ thClass: DEFAULT_TH_CLASSES,
+ tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
+ columnClass: 'gl-w-10p',
+ thAttr: { 'data-testid': 'pipeline-th' },
+ },
+ {
+ key: 'triggerer',
+ label: s__('Pipeline|Triggerer'),
+ thClass: DEFAULT_TH_CLASSES,
+ tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
+ columnClass: 'gl-w-10p',
+ thAttr: { 'data-testid': 'triggerer-th' },
+ },
+ {
+ key: 'commit',
+ label: s__('Pipeline|Commit'),
+ thClass: DEFAULT_TH_CLASSES,
+ tdClass: DEFAULT_TD_CLASS,
+ columnClass: 'gl-w-20p',
+ thAttr: { 'data-testid': 'commit-th' },
+ },
+ {
+ key: 'stages',
+ label: s__('Pipeline|Stages'),
+ thClass: DEFAULT_TH_CLASSES,
+ tdClass: DEFAULT_TD_CLASS,
+ columnClass: 'gl-w-15p',
+ thAttr: { 'data-testid': 'stages-th' },
+ },
+ {
+ key: 'timeago',
+ label: s__('Pipeline|Duration'),
+ thClass: DEFAULT_TH_CLASSES,
+ tdClass: DEFAULT_TD_CLASS,
+ columnClass: 'gl-w-15p',
+ thAttr: { 'data-testid': 'timeago-th' },
+ },
+ {
+ key: 'actions',
+ label: '',
+ thClass: DEFAULT_TH_CLASSES,
+ tdClass: DEFAULT_TD_CLASS,
+ columnClass: 'gl-w-20p',
+ thAttr: { 'data-testid': 'actions-th' },
+ },
+ ],
components: {
GlTable,
- PipelinesTableRowComponent,
+ PipelinesCommit,
+ PipelineOperations,
+ PipelineStage,
+ PipelinesStatusBadge,
PipelineStopModal,
+ PipelinesTableRowComponent,
+ PipelinesTimeago,
+ PipelineTriggerer,
+ PipelineUrl,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -43,11 +121,6 @@ export default {
cancelingPipeline: null,
};
},
- computed: {
- legacyTableClass() {
- return !this.glFeatures.newPipelinesTable ? 'ci-table' : '';
- },
- },
watch: {
pipelines() {
this.cancelingPipeline = null;
@@ -73,8 +146,8 @@ export default {
};
</script>
<template>
- <div :class="legacyTableClass">
- <div v-if="!glFeatures.newPipelinesTable" data-testid="ci-table">
+ <div class="ci-table">
+ <div v-if="!glFeatures.newPipelinesTable" data-testid="legacy-ci-table">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-10 js-pipeline-status" role="rowheader">
{{ s__('Pipeline|Status') }}
@@ -107,7 +180,71 @@ export default {
/>
</div>
- <gl-table v-else />
+ <gl-table
+ v-else
+ :fields="$options.fields"
+ :items="pipelines"
+ tbody-tr-class="commit"
+ :tbody-tr-attr="{ 'data-testid': 'pipeline-table-row' }"
+ stacked="lg"
+ fixed
+ >
+ <template #head(actions)>
+ <slot name="table-header-actions"></slot>
+ </template>
+
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(status)="{ item }">
+ <pipelines-status-badge :pipeline="item" :view-type="viewType" />
+ </template>
+
+ <template #cell(pipeline)="{ item }">
+ <pipeline-url
+ class="gl-text-truncate"
+ :pipeline="item"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ />
+ </template>
+
+ <template #cell(triggerer)="{ item }">
+ <pipeline-triggerer :pipeline="item" />
+ </template>
+
+ <template #cell(commit)="{ item }">
+ <pipelines-commit :pipeline="item" :view-type="viewType" />
+ </template>
+
+ <template #cell(stages)="{ item }">
+ <div class="stage-cell">
+ <div></div>
+ <template v-if="item.details.stages.length > 0">
+ <div
+ v-for="(stage, index) in item.details.stages"
+ :key="index"
+ class="stage-container dropdown"
+ data-testid="widget-mini-pipeline-graph"
+ >
+ <pipeline-stage
+ :type="$options.pipelinesTable"
+ :stage="stage"
+ :update-dropdown="updateGraphDropdown"
+ />
+ </div>
+ </template>
+ </div>
+ </template>
+
+ <template #cell(timeago)="{ item }">
+ <pipelines-timeago :pipeline="item" />
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <pipeline-operations :pipeline="item" :canceling-pipeline="cancelingPipeline" />
+ </template>
+ </gl-table>
<pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
</div>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 9699f57b4fc..21b114825a6 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -33,3 +33,5 @@ export const LOAD_FAILURE = 'load_failure';
export const PARSE_FAILURE = 'parse_failure';
export const POST_FAILURE = 'post_failure';
export const UNSUPPORTED_DATA = 'unsupported_data';
+
+export const CHILD_VIEW = 'child';
diff --git a/app/assets/javascripts/ref/components/ref_results_section.vue b/app/assets/javascripts/ref/components/ref_results_section.vue
index 87ce4f1a49c..4fa2a92ff03 100644
--- a/app/assets/javascripts/ref/components/ref_results_section.vue
+++ b/app/assets/javascripts/ref/components/ref_results_section.vue
@@ -11,6 +11,12 @@ export default {
GlIcon,
},
props: {
+ showHeader: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+
sectionTitle: {
type: String,
required: true,
@@ -84,7 +90,7 @@ export default {
<template>
<div>
- <gl-dropdown-section-header>
+ <gl-dropdown-section-header v-if="showHeader">
<div class="gl-display-flex align-items-center" data-testid="section-header">
<span class="gl-mr-2 gl-mb-1">{{ sectionTitle }}</span>
<gl-badge variant="neutral">{{ totalCountText }}</gl-badge>
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
index 8f2805b36f6..36881552216 100644
--- a/app/assets/javascripts/ref/components/ref_selector.vue
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -8,9 +8,16 @@ import {
GlIcon,
GlLoadingIcon,
} from '@gitlab/ui';
-import { debounce } from 'lodash';
+import { debounce, isArray } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
-import { SEARCH_DEBOUNCE_MS, DEFAULT_I18N } from '../constants';
+import {
+ ALL_REF_TYPES,
+ SEARCH_DEBOUNCE_MS,
+ DEFAULT_I18N,
+ REF_TYPE_BRANCHES,
+ REF_TYPE_TAGS,
+ REF_TYPE_COMMITS,
+} from '../constants';
import createStore from '../stores';
import RefResultsSection from './ref_results_section.vue';
@@ -28,6 +35,20 @@ export default {
RefResultsSection,
},
props: {
+ enabledRefTypes: {
+ type: Array,
+ required: false,
+ default: () => ALL_REF_TYPES,
+ validator: (val) =>
+ // It has to be an arrray
+ isArray(val) &&
+ // with at least one item
+ val.length > 0 &&
+ // and only "REF_TYPE_BRANCHES", "REF_TYPE_TAGS", and "REF_TYPE_COMMITS" are allowed
+ val.every((item) => ALL_REF_TYPES.includes(item)) &&
+ // and no duplicates are allowed
+ val.length === new Set(val).size,
+ },
value: {
type: String,
required: false,
@@ -62,17 +83,29 @@ export default {
};
},
showBranchesSection() {
- return Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error);
+ return (
+ this.enabledRefTypes.includes(REF_TYPE_BRANCHES) &&
+ Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error)
+ );
},
showTagsSection() {
- return Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error);
+ return (
+ this.enabledRefTypes.includes(REF_TYPE_TAGS) &&
+ Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error)
+ );
},
showCommitsSection() {
- return Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error);
+ return (
+ this.enabledRefTypes.includes(REF_TYPE_COMMITS) &&
+ Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error)
+ );
},
showNoResults() {
return !this.showBranchesSection && !this.showTagsSection && !this.showCommitsSection;
},
+ showSectionHeaders() {
+ return this.enabledRefTypes.length > 1;
+ },
},
watch: {
// Keep the Vuex store synchronized if the parent
@@ -97,10 +130,18 @@ export default {
}, SEARCH_DEBOUNCE_MS);
this.setProjectId(this.projectId);
- this.search(this.query);
+
+ this.$watch(
+ 'enabledRefTypes',
+ () => {
+ this.setEnabledRefTypes(this.enabledRefTypes);
+ this.search(this.query);
+ },
+ { immediate: true },
+ );
},
methods: {
- ...mapActions(['setProjectId', 'setSelectedRef', 'search']),
+ ...mapActions(['setEnabledRefTypes', 'setProjectId', 'setSelectedRef', 'search']),
focusSearchBox() {
this.$refs.searchBox.$el.querySelector('input').focus();
},
@@ -170,6 +211,7 @@ export default {
:selected-ref="selectedRef"
:error="matches.branches.error"
:error-message="i18n.branchesErrorMessage"
+ :show-header="showSectionHeaders"
data-testid="branches-section"
@selected="selectRef($event)"
/>
@@ -185,6 +227,7 @@ export default {
:selected-ref="selectedRef"
:error="matches.tags.error"
:error-message="i18n.tagsErrorMessage"
+ :show-header="showSectionHeaders"
data-testid="tags-section"
@selected="selectRef($event)"
/>
@@ -200,6 +243,7 @@ export default {
:selected-ref="selectedRef"
:error="matches.commits.error"
:error-message="i18n.commitsErrorMessage"
+ :show-header="showSectionHeaders"
data-testid="commits-section"
@selected="selectRef($event)"
/>
diff --git a/app/assets/javascripts/ref/constants.js b/app/assets/javascripts/ref/constants.js
index ca82b951377..44d0f50b832 100644
--- a/app/assets/javascripts/ref/constants.js
+++ b/app/assets/javascripts/ref/constants.js
@@ -1,5 +1,10 @@
import { __ } from '~/locale';
+export const REF_TYPE_BRANCHES = 'REF_TYPE_BRANCHES';
+export const REF_TYPE_TAGS = 'REF_TYPE_TAGS';
+export const REF_TYPE_COMMITS = 'REF_TYPE_COMMITS';
+export const ALL_REF_TYPES = Object.freeze([REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS]);
+
export const X_TOTAL_HEADER = 'x-total';
export const SEARCH_DEBOUNCE_MS = 250;
diff --git a/app/assets/javascripts/ref/stores/actions.js b/app/assets/javascripts/ref/stores/actions.js
index d9bdd64ace5..3832cc0c21d 100644
--- a/app/assets/javascripts/ref/stores/actions.js
+++ b/app/assets/javascripts/ref/stores/actions.js
@@ -1,17 +1,26 @@
import Api from '~/api';
+import { REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS } from '../constants';
import * as types from './mutation_types';
+export const setEnabledRefTypes = ({ commit }, refTypes) =>
+ commit(types.SET_ENABLED_REF_TYPES, refTypes);
+
export const setProjectId = ({ commit }, projectId) => commit(types.SET_PROJECT_ID, projectId);
export const setSelectedRef = ({ commit }, selectedRef) =>
commit(types.SET_SELECTED_REF, selectedRef);
-export const search = ({ dispatch, commit }, query) => {
+export const search = ({ state, dispatch, commit }, query) => {
commit(types.SET_QUERY, query);
- dispatch('searchBranches');
- dispatch('searchTags');
- dispatch('searchCommits');
+ const dispatchIfRefTypeEnabled = (refType, action) => {
+ if (state.enabledRefTypes.includes(refType)) {
+ dispatch(action);
+ }
+ };
+ dispatchIfRefTypeEnabled(REF_TYPE_BRANCHES, 'searchBranches');
+ dispatchIfRefTypeEnabled(REF_TYPE_TAGS, 'searchTags');
+ dispatchIfRefTypeEnabled(REF_TYPE_COMMITS, 'searchCommits');
};
export const searchBranches = ({ commit, state }) => {
diff --git a/app/assets/javascripts/ref/stores/mutation_types.js b/app/assets/javascripts/ref/stores/mutation_types.js
index 9f6195f5f3f..c26f4fa00c7 100644
--- a/app/assets/javascripts/ref/stores/mutation_types.js
+++ b/app/assets/javascripts/ref/stores/mutation_types.js
@@ -1,3 +1,5 @@
+export const SET_ENABLED_REF_TYPES = 'SET_ENABLED_REF_TYPES';
+
export const SET_PROJECT_ID = 'SET_PROJECT_ID';
export const SET_SELECTED_REF = 'SET_SELECTED_REF';
export const SET_QUERY = 'SET_QUERY';
diff --git a/app/assets/javascripts/ref/stores/mutations.js b/app/assets/javascripts/ref/stores/mutations.js
index 4dc73dabfe2..f91cbae8462 100644
--- a/app/assets/javascripts/ref/stores/mutations.js
+++ b/app/assets/javascripts/ref/stores/mutations.js
@@ -4,6 +4,9 @@ import { X_TOTAL_HEADER } from '../constants';
import * as types from './mutation_types';
export default {
+ [types.SET_ENABLED_REF_TYPES](state, refTypes) {
+ state.enabledRefTypes = refTypes;
+ },
[types.SET_PROJECT_ID](state, projectId) {
state.projectId = projectId;
},
diff --git a/app/assets/javascripts/ref/stores/state.js b/app/assets/javascripts/ref/stores/state.js
index 65b9d6449d7..3affa8f8d03 100644
--- a/app/assets/javascripts/ref/stores/state.js
+++ b/app/assets/javascripts/ref/stores/state.js
@@ -1,23 +1,18 @@
+const createRefTypeState = () => ({
+ list: [],
+ totalCount: 0,
+ error: null,
+});
+
export default () => ({
+ enabledRefTypes: [],
projectId: null,
query: '',
matches: {
- branches: {
- list: [],
- totalCount: 0,
- error: null,
- },
- tags: {
- list: [],
- totalCount: 0,
- error: null,
- },
- commits: {
- list: [],
- totalCount: 0,
- error: null,
- },
+ branches: createRefTypeState(),
+ tags: createRefTypeState(),
+ commits: createRefTypeState(),
},
selectedRef: null,
requestCount: 0,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 58086922fde..40312f38e08 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:codequality_backend_comparison, @project, default_enabled: :yaml)
push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:new_pipelines_table, @project, default_enabled: :yaml)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 67cb2b9558d..dab2f3bd67a 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -46,6 +46,8 @@ class RootController < Dashboard::ProjectsController
redirect_to(activity_dashboard_path)
when 'starred_project_activity'
redirect_to(activity_dashboard_path(filter: 'starred'))
+ when 'followed_user_activity'
+ redirect_to(activity_dashboard_path(filter: 'followed'))
when 'groups'
redirect_to(dashboard_groups_path)
when 'todos'
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index c2a77abb9c9..12bc509466e 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -29,6 +29,7 @@ module PreferencesHelper
stars: _("Starred Projects"),
project_activity: _("Your Projects' Activity"),
starred_project_activity: _("Starred Projects' Activity"),
+ followed_user_activity: _("Followed Users' Activity"),
groups: _("Your Groups"),
todos: _("Your To-Do List"),
issues: _("Assigned Issues"),
diff --git a/app/models/user.rb b/app/models/user.rb
index 9b288d99a7f..dfaef473cc9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -272,7 +272,7 @@ class User < ApplicationRecord
enum layout: { fixed: 0, fluid: 1 }
# User's Dashboard preference
- enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8 }
+ enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8, followed_user_activity: 9 }
# User's Project preference
enum project_view: { readme: 0, activity: 1, files: 2 }
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 4301bf01858..f52bf1551f4 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -25,7 +25,11 @@
&bull;
- if total_count > recent_releases.count
&bull;
- = link_to n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }, project_releases_path(milestone.project)
+ - more_text = n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }
+ - if milestone.project_milestone?
+ = link_to more_text, project_releases_path(milestone.project)
+ - else
+ = more_text
%div
= render('shared/milestone_expired', milestone: milestone)
- if milestone.group_milestone?