summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pipelines/components/pipelines_list
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pipelines/components/pipelines_list')
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue30
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue39
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/job_item.vue190
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue28
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue143
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue38
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue269
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue74
13 files changed, 426 insertions, 425 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
deleted file mode 100644
index 6c3a4a27606..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-<script>
-export default {
- name: 'PipelinesSvgState',
- props: {
- svgPath: {
- type: String,
- required: true,
- },
-
- message: {
- type: String,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div class="row empty-state">
- <div class="col-12">
- <div class="svg-content"><img :src="svgPath" /></div>
- </div>
-
- <div class="col-12 text-center">
- <div class="text-content">
- <h4>{{ message }}</h4>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index f8107d288d9..c3bcfcb18fb 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
@@ -1,7 +1,9 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
+import Experiment from '~/experimentation/components/experiment.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
+import PipelinesCiTemplates from './pipelines_ci_templates.vue';
export default {
i18n: {
@@ -15,6 +17,8 @@ export default {
name: 'PipelinesEmptyState',
components: {
GlEmptyState,
+ Experiment,
+ PipelinesCiTemplates,
},
props: {
emptyStateSvgPath: {
@@ -35,19 +39,26 @@ export default {
</script>
<template>
<div>
- <gl-empty-state
- v-if="canSetCi"
- :title="$options.i18n.title"
- :svg-path="emptyStateSvgPath"
- :description="$options.i18n.description"
- :primary-button-text="$options.i18n.btnText"
- :primary-button-link="ciHelpPagePath"
- />
- <gl-empty-state
- v-else
- title=""
- :svg-path="emptyStateSvgPath"
- :description="$options.i18n.noCiDescription"
- />
+ <experiment name="pipeline_empty_state_templates">
+ <template #control>
+ <gl-empty-state
+ v-if="canSetCi"
+ :title="$options.i18n.title"
+ :svg-path="emptyStateSvgPath"
+ :description="$options.i18n.description"
+ :primary-button-text="$options.i18n.btnText"
+ :primary-button-link="ciHelpPagePath"
+ />
+ <gl-empty-state
+ v-else
+ title=""
+ :svg-path="emptyStateSvgPath"
+ :description="$options.i18n.noCiDescription"
+ />
+ </template>
+ <template #candidate>
+ <pipelines-ci-templates />
+ </template>
+ </experiment>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/job_item.vue b/app/assets/javascripts/pipelines/components/pipelines_list/job_item.vue
new file mode 100644
index 00000000000..670fa398536
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/job_item.vue
@@ -0,0 +1,190 @@
+<script>
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
+import { sprintf } from '~/locale';
+import { reportToSentry } from '../../utils';
+import ActionComponent from '../jobs_shared/action_component.vue';
+import JobNameComponent from '../jobs_shared/job_name_component.vue';
+
+/**
+ * Renders the badge for the pipeline graph and the job's dropdown.
+ *
+ * The following object should be provided as `job`:
+ *
+ * {
+ * "id": 4256,
+ * "name": "test",
+ * "status": {
+ * "icon": "status_success",
+ * "text": "passed",
+ * "label": "passed",
+ * "group": "success",
+ * "tooltip": "passed",
+ * "details_path": "/root/ci-mock/builds/4256",
+ * "action": {
+ * "icon": "retry",
+ * "title": "Retry",
+ * "path": "/root/ci-mock/builds/4256/retry",
+ * "method": "post"
+ * }
+ * }
+ * }
+ */
+
+export default {
+ hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
+ components: {
+ ActionComponent,
+ JobNameComponent,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [delayedJobMixin],
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ cssClassJobName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ dropdownLength: {
+ type: Number,
+ required: false,
+ default: Infinity,
+ },
+ jobHovered: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ pipelineExpanded: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ pipelineId: {
+ type: Number,
+ required: false,
+ default: -1,
+ },
+ },
+ computed: {
+ boundary() {
+ return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
+ },
+ detailsPath() {
+ return this.status.details_path;
+ },
+ hasDetails() {
+ return this.status.has_details;
+ },
+ status() {
+ return this.job && this.job.status ? this.job.status : {};
+ },
+ tooltipText() {
+ const textBuilder = [];
+ const { name: jobName } = this.job;
+
+ if (jobName) {
+ textBuilder.push(jobName);
+ }
+
+ const { tooltip: statusTooltip } = this.status;
+ if (jobName && statusTooltip) {
+ textBuilder.push('-');
+ }
+
+ if (statusTooltip) {
+ if (this.isDelayedJob) {
+ textBuilder.push(sprintf(statusTooltip, { remainingTime: this.remainingTime }));
+ } else {
+ textBuilder.push(statusTooltip);
+ }
+ }
+
+ return textBuilder.join(' ');
+ },
+ /**
+ * Verifies if the provided job has an action path
+ *
+ * @return {Boolean}
+ */
+ hasAction() {
+ return this.job.status && this.job.status.action && this.job.status.action.path;
+ },
+ relatedDownstreamHovered() {
+ return this.job.name === this.jobHovered;
+ },
+ relatedDownstreamExpanded() {
+ return this.job.name === this.pipelineExpanded.jobName && this.pipelineExpanded.expanded;
+ },
+ jobClasses() {
+ return this.relatedDownstreamHovered || this.relatedDownstreamExpanded
+ ? `${this.$options.hoverClass} ${this.cssClassJobName}`
+ : this.cssClassJobName;
+ },
+ },
+ errorCaptured(err, _vm, info) {
+ reportToSentry('pipelines_job_item', `pipelines_job_item error: ${err}, info: ${info}`);
+ },
+ methods: {
+ hideTooltips() {
+ this.$root.$emit(BV_HIDE_TOOLTIP);
+ },
+ pipelineActionRequestComplete() {
+ this.$emit('pipelineActionRequestComplete');
+ },
+ },
+};
+</script>
+<template>
+ <div
+ class="ci-job-component gl-display-flex gl-align-items-center gl-justify-content-space-between"
+ data-qa-selector="job_item_container"
+ >
+ <gl-link
+ v-if="hasDetails"
+ v-gl-tooltip="{
+ boundary: 'viewport',
+ placement: 'bottom',
+ customClass: 'gl-pointer-events-none',
+ }"
+ :href="detailsPath"
+ :title="tooltipText"
+ :class="jobClasses"
+ class="js-pipeline-graph-job-link qa-job-link menu-item gl-text-gray-900 gl-active-text-decoration-none gl-focus-text-decoration-none gl-hover-text-decoration-none"
+ data-testid="job-with-link"
+ @click.stop="hideTooltips"
+ @mouseout="hideTooltips"
+ >
+ <job-name-component :name="job.name" :status="job.status" :icon-size="24" />
+ </gl-link>
+
+ <div
+ v-else
+ v-gl-tooltip="{ boundary, placement: 'bottom', customClass: 'gl-pointer-events-none' }"
+ :title="tooltipText"
+ :class="jobClasses"
+ class="js-job-component-tooltip non-details-job-component menu-item"
+ data-testid="job-without-link"
+ @mouseout="hideTooltips"
+ >
+ <job-name-component :name="job.name" :status="job.status" :icon-size="24" />
+ </div>
+
+ <action-component
+ v-if="hasAction"
+ :tooltip-text="status.action.title"
+ :link="status.action.path"
+ :action-icon="status.action.icon"
+ data-qa-selector="action_button"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
index cf0849751df..235126fea0c 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
@@ -41,29 +41,29 @@ export default {
<template>
<div class="nav-controls">
<gl-button
- v-if="newPipelinePath"
- :href="newPipelinePath"
- variant="success"
- category="primary"
- class="js-run-pipeline"
- data-testid="run-pipeline-button"
- data-qa-selector="run_pipeline_button"
- >
- {{ s__('Pipelines|Run Pipeline') }}
- </gl-button>
-
- <gl-button
v-if="resetCachePath"
:loading="isResetCacheButtonLoading"
class="js-clear-cache"
data-testid="clear-cache-button"
@click="onClickResetCache"
>
- {{ s__('Pipelines|Clear Runner Caches') }}
+ {{ s__('Pipelines|Clear runner caches') }}
</gl-button>
<gl-button v-if="ciLintPath" :href="ciLintPath" class="js-ci-lint" data-testid="ci-lint-button">
- {{ s__('Pipelines|CI Lint') }}
+ {{ s__('Pipelines|CI lint') }}
+ </gl-button>
+
+ <gl-button
+ v-if="newPipelinePath"
+ :href="newPipelinePath"
+ variant="confirm"
+ category="primary"
+ class="js-run-pipeline"
+ data-testid="run-pipeline-button"
+ data-qa-selector="run_pipeline_button"
+ >
+ {{ s__('Pipeline|Run pipeline') }}
</gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
index 05372010d0f..2b33467e948 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
@@ -36,7 +36,7 @@ export default {
};
</script>
<template>
- <div data-testid="widget-mini-pipeline-graph">
+ <div data-testid="pipeline-mini-graph">
<div
v-for="stage in stages"
:key="stage.name"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
index bdb7dd06620..bf992b84387 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
@@ -17,7 +17,7 @@ import { deprecatedCreateFlash as Flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import eventHub from '../../event_hub';
-import JobItem from '../graph/job_item.vue';
+import JobItem from './job_item.vue';
export default {
components: {
@@ -103,7 +103,7 @@ export default {
<template>
<gl-dropdown
ref="dropdown"
- v-gl-tooltip.hover
+ v-gl-tooltip.hover.ds0
data-testid="mini-pipeline-graph-dropdown"
:title="stage.title"
variant="link"
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 c707b395192..0528e4c147c 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -17,19 +17,11 @@ export default {
user() {
return this.pipeline.user;
},
- classes() {
- const triggererClass = 'pipeline-triggerer';
-
- if (this.glFeatures.newPipelinesTable) {
- return triggererClass;
- }
- return `table-section section-10 d-none d-md-block ${triggererClass}`;
- },
},
};
</script>
<template>
- <div :class="classes" data-testid="pipeline-triggerer">
+ <div class="pipeline-triggerer" 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 0de520a2ca7..d39e120dc6c 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -49,19 +49,11 @@ export default {
autoDevopsHelpPath() {
return helpPagePath('topics/autodevops/index.md');
},
- classes() {
- const tagsClass = 'pipeline-tags';
-
- if (this.glFeatures.newPipelinesTable) {
- return tagsClass;
- }
- return `table-section section-10 d-none d-md-block ${tagsClass}`;
- },
},
};
</script>
<template>
- <div :class="classes" data-testid="pipeline-url-table-cell">
+ <div class="pipeline-tags" 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.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index 19d93e7d083..f14a582d731 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
+import { GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { isEqual } from 'lodash';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { getParameterByName } from '~/lib/utils/common_utils';
@@ -10,7 +10,6 @@ import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../
import PipelinesMixin from '../../mixins/pipelines_mixin';
import PipelinesService from '../../services/pipelines_service';
import { validateParams } from '../../utils';
-import SvgBlankState from './blank_state.vue';
import EmptyState from './empty_state.vue';
import NavigationControls from './nav_controls.vue';
import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
@@ -19,13 +18,13 @@ import PipelinesTableComponent from './pipelines_table.vue';
export default {
components: {
EmptyState,
+ GlEmptyState,
GlIcon,
GlLoadingIcon,
NavigationTabs,
NavigationControls,
PipelinesFilteredSearch,
PipelinesTableComponent,
- SvgBlankState,
TablePagination,
},
mixins: [PipelinesMixin],
@@ -314,6 +313,7 @@ export default {
</div>
<pipelines-filtered-search
+ v-if="stateToRender !== $options.stateMap.emptyState"
:project-id="projectId"
:params="validatedParams"
@filterPipelines="filterPipelines"
@@ -333,19 +333,19 @@ export default {
:can-set-ci="canCreatePipeline"
/>
- <svg-blank-state
+ <gl-empty-state
v-else-if="stateToRender === $options.stateMap.error"
:svg-path="errorStateSvgPath"
- :message="
+ :title="
s__(`Pipelines|There was an error fetching the pipelines.
Try again in a few moments or contact your support team.`)
"
/>
- <svg-blank-state
+ <gl-empty-state
v-else-if="stateToRender === $options.stateMap.emptyTab"
:svg-path="noPipelinesSvgPath"
- :message="emptyTabMessage"
+ :title="emptyTabMessage"
/>
<div v-else-if="stateToRender === $options.stateMap.tableList">
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue
new file mode 100644
index 00000000000..c2ec8c57fd7
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue
@@ -0,0 +1,143 @@
+<script>
+import { GlButton, GlCard, GlSprintf } from '@gitlab/ui';
+import ExperimentTracking from '~/experimentation/experiment_tracking';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
+import { s__, sprintf } from '~/locale';
+import { HELLO_WORLD_TEMPLATE_KEY } from '../../constants';
+
+export default {
+ components: {
+ GlButton,
+ GlCard,
+ GlSprintf,
+ },
+ HELLO_WORLD_TEMPLATE_KEY,
+ i18n: {
+ cta: s__('Pipelines|Use template'),
+ testTemplates: {
+ title: s__('Pipelines|Use a sample CI/CD template'),
+ subtitle: s__(
+ 'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
+ ),
+ helloWorld: {
+ title: s__('Pipelines|“Hello world” with GitLab CI/CD'),
+ description: s__(
+ 'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a simple pipeline that runs a “Hello world” script.',
+ ),
+ },
+ },
+ templates: {
+ title: s__('Pipelines|Use a CI/CD template'),
+ subtitle: s__(
+ "Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD.",
+ ),
+ description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
+ },
+ },
+ inject: ['addCiYmlPath', 'suggestedCiTemplates'],
+ data() {
+ const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
+ return {
+ name,
+ logo,
+ link: mergeUrlParams({ template: name }, this.addCiYmlPath),
+ description: sprintf(this.$options.i18n.templates.description, { name }),
+ };
+ });
+
+ return {
+ templates,
+ helloWorldTemplateUrl: mergeUrlParams(
+ { template: HELLO_WORLD_TEMPLATE_KEY },
+ this.addCiYmlPath,
+ ),
+ };
+ },
+ methods: {
+ trackEvent(template) {
+ const tracking = new ExperimentTracking('pipeline_empty_state_templates', {
+ label: template,
+ });
+ tracking.event('template_clicked');
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.testTemplates.title }}</h2>
+ <p class="gl-text-gray-800 gl-mb-6">
+ <gl-sprintf :message="$options.i18n.testTemplates.subtitle">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <div class="row gl-mb-8">
+ <div class="col-lg-3">
+ <gl-card>
+ <div class="gl-flex-direction-row">
+ <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
+ <div class="gl-mb-3">
+ <strong class="gl-text-gray-800 gl-mb-2">{{
+ $options.i18n.testTemplates.helloWorld.title
+ }}</strong>
+ </div>
+ <p class="gl-font-sm">{{ $options.i18n.testTemplates.helloWorld.description }}</p>
+ </div>
+
+ <gl-button
+ category="primary"
+ variant="confirm"
+ :href="helloWorldTemplateUrl"
+ data-testid="test-template-link"
+ @click="trackEvent($options.HELLO_WORLD_TEMPLATE_KEY)"
+ >
+ {{ $options.i18n.cta }}
+ </gl-button>
+ </gl-card>
+ </div>
+ </div>
+
+ <h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.templates.title }}</h2>
+ <p class="gl-text-gray-800 gl-mb-6">{{ $options.i18n.templates.subtitle }}</p>
+
+ <ul class="gl-list-style-none gl-pl-0">
+ <li v-for="template in templates" :key="template.name">
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
+ >
+ <div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
+ <img
+ width="64"
+ height="64"
+ :src="template.logo"
+ class="gl-mr-6"
+ data-testid="template-logo"
+ />
+ <div class="gl-flex-direction-row">
+ <div class="gl-mb-3">
+ <strong class="gl-text-gray-800" data-testid="template-name">{{
+ template.name
+ }}</strong>
+ </div>
+ <p class="gl-mb-0 gl-font-sm" data-testid="template-description">
+ {{ template.description }}
+ </p>
+ </div>
+ </div>
+ <gl-button
+ category="primary"
+ variant="confirm"
+ :href="template.link"
+ data-testid="template-link"
+ @click="trackEvent(template.name)"
+ >
+ {{ $options.i18n.cta }}
+ </gl-button>
+ </div>
+ </li>
+ </ul>
+ </div>
+</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 aa27aa7e50d..47fc7023222 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -1,7 +1,6 @@
<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 PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineOperations from './pipeline_operations.vue';
@@ -10,7 +9,6 @@ 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 PipelinesTimeago from './time_ago.vue';
const DEFAULT_TD_CLASS = 'gl-p-5!';
@@ -83,7 +81,6 @@ export default {
PipelineOperations,
PipelinesStatusBadge,
PipelineStopModal,
- PipelinesTableRowComponent,
PipelinesTimeago,
PipelineTriggerer,
PipelineUrl,
@@ -91,7 +88,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [glFeatureFlagMixin()],
props: {
pipelines: {
type: Array,
@@ -149,41 +145,7 @@ export default {
</script>
<template>
<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') }}
- </div>
- <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
- {{ s__('Pipeline|Pipeline') }}
- </div>
- <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
- {{ s__('Pipeline|Triggerer') }}
- </div>
- <div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
- {{ s__('Pipeline|Commit') }}
- </div>
- <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
- {{ s__('Pipeline|Stages') }}
- </div>
- <div class="table-section section-15" role="rowheader"></div>
- <div class="table-section section-20" role="rowheader">
- <slot name="table-header-actions"></slot>
- </div>
- </div>
- <pipelines-table-row-component
- v-for="model in pipelines"
- :key="model.id"
- :pipeline="model"
- :pipeline-schedule-url="pipelineScheduleUrl"
- :update-graph-dropdown="updateGraphDropdown"
- :view-type="viewType"
- :canceling-pipeline="cancelingPipeline"
- />
- </div>
-
<gl-table
- v-else
:fields="$options.fields"
:items="pipelines"
tbody-tr-class="commit"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
deleted file mode 100644
index f684a0b0fcd..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
+++ /dev/null
@@ -1,269 +0,0 @@
-<script>
-import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
-import { __ } from '~/locale';
-import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
-import CommitComponent from '~/vue_shared/components/commit.vue';
-import eventHub from '../../event_hub';
-import PipelineMiniGraph from './pipeline_mini_graph.vue';
-import PipelineTriggerer from './pipeline_triggerer.vue';
-import PipelineUrl from './pipeline_url.vue';
-import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
-import PipelinesManualActionsComponent from './pipelines_manual_actions.vue';
-import PipelinesTimeago from './time_ago.vue';
-
-export default {
- i18n: {
- cancelTitle: __('Cancel'),
- redeployTitle: __('Retry'),
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- GlModalDirective,
- },
- components: {
- PipelinesManualActionsComponent,
- PipelinesArtifactsComponent,
- CommitComponent,
- PipelineMiniGraph,
- PipelineUrl,
- PipelineTriggerer,
- CiBadge,
- PipelinesTimeago,
- GlButton,
- },
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- pipelineScheduleUrl: {
- type: String,
- required: false,
- default: '',
- },
- updateGraphDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
- viewType: {
- type: String,
- required: true,
- },
- cancelingPipeline: {
- type: Number,
- required: false,
- default: null,
- },
- },
- data() {
- return {
- isRetrying: false,
- };
- },
- computed: {
- actions() {
- if (!this.pipeline || !this.pipeline.details) {
- return [];
- }
- const { details } = this.pipeline;
- return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
- },
- /**
- * If provided, returns the commit tag.
- * Needed to render the commit component column.
- *
- * This field needs a lot of verification, because of different possible cases:
- *
- * 1. person who is an author of a commit might be a GitLab user
- * 2. if person who is an author of a commit is a GitLab user, they can have a GitLab avatar
- * 3. If GitLab user does not have avatar they might have a Gravatar
- * 4. If committer is not a GitLab User they can have a Gravatar
- * 5. We do not have consistent API object in this case
- * 6. We should improve API and the code
- *
- * @returns {Object|Undefined}
- */
- 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;
- },
- pipelineStatus() {
- return this.pipeline?.details?.status ?? {};
- },
- hasStages() {
- return this.pipeline?.details?.stages?.length > 0;
- },
- displayPipelineActions() {
- return (
- this.pipeline.flags.retryable ||
- this.pipeline.flags.cancelable ||
- this.pipeline.details.manual_actions.length ||
- this.pipeline.details.artifacts.length
- );
- },
- isChildView() {
- return this.viewType === 'child';
- },
- 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);
- },
- handlePipelineActionRequestComplete() {
- // warn the pipelines table to update
- eventHub.$emit('refreshPipelinesTable');
- },
- },
-};
-</script>
-<template>
- <div class="commit gl-responsive-table-row">
- <div class="table-section section-10 commit-link">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Status') }}</div>
- <div class="table-mobile-content">
- <ci-badge
- :status="pipelineStatus"
- :show-text="!isChildView"
- :icon-classes="'gl-vertical-align-middle!'"
- data-qa-selector="pipeline_commit_status"
- />
- </div>
- </div>
-
- <pipeline-url :pipeline="pipeline" :pipeline-schedule-url="pipelineScheduleUrl" />
- <pipeline-triggerer :pipeline="pipeline" />
-
- <div class="table-section section-wrap section-20">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Commit') }}</div>
- <div class="table-mobile-content">
- <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"
- />
- </div>
- </div>
-
- <div class="table-section section-wrap section-15 stage-cell">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Stages') }}</div>
- <div class="table-mobile-content">
- <pipeline-mini-graph
- v-if="hasStages"
- :stages="pipeline.details.stages"
- :update-dropdown="updateGraphDropdown"
- @pipelineActionRequestComplete="handlePipelineActionRequestComplete"
- />
- </div>
- </div>
-
- <pipelines-timeago class="gl-text-right" :pipeline="pipeline" />
-
- <div
- v-if="displayPipelineActions"
- class="table-section section-20 table-button-footer pipeline-actions"
- >
- <div class="btn-group table-action-buttons">
- <pipelines-manual-actions-component 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>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index 543bdf94307..e6b03751350 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -22,6 +22,12 @@ export default {
finishedTime() {
return this.pipeline?.details?.finished_at;
},
+ skipped() {
+ return this.pipeline?.details?.status?.label === 'skipped';
+ },
+ stuck() {
+ return this.pipeline.flags.stuck;
+ },
durationFormatted() {
const date = new Date(this.duration * 1000);
@@ -42,46 +48,50 @@ export default {
return `${hh}:${mm}:${ss}`;
},
- legacySectionClass() {
- return !this.glFeatures.newPipelinesTable ? 'table-section section-15' : '';
- },
- legacyTableMobileClass() {
- return !this.glFeatures.newPipelinesTable ? 'table-mobile-content' : '';
- },
showInProgress() {
- return !this.duration && !this.finishedTime;
+ return !this.duration && !this.finishedTime && !this.skipped;
+ },
+ showSkipped() {
+ return !this.duration && !this.finishedTime && this.skipped;
},
},
};
</script>
<template>
- <div :class="legacySectionClass">
- <div v-if="!glFeatures.newPipelinesTable" class="table-mobile-header" role="rowheader">
- {{ s__('Pipeline|Duration') }}
- </div>
- <div :class="legacyTableMobileClass">
- <span v-if="showInProgress" data-testid="pipeline-in-progress">
- <gl-icon name="hourglass" class="gl-vertical-align-baseline! gl-mr-2" :size="12" />
- {{ s__('Pipeline|In progress') }}
- </span>
+ <div>
+ <span v-if="showInProgress" data-testid="pipeline-in-progress">
+ <gl-icon v-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" />
+ <gl-icon
+ v-else
+ name="hourglass"
+ class="gl-vertical-align-baseline! gl-mr-2"
+ :size="12"
+ data-testid="hourglass-icon"
+ />
+ {{ s__('Pipeline|In progress') }}
+ </span>
+
+ <span v-if="showSkipped" data-testid="pipeline-skipped">
+ <gl-icon name="status_skipped_borderless" class="gl-mr-2" :size="16" />
+ {{ s__('Pipeline|Skipped') }}
+ </span>
- <p v-if="duration" class="duration">
- <gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
- {{ durationFormatted }}
- </p>
+ <p v-if="duration" class="duration">
+ <gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
+ {{ durationFormatted }}
+ </p>
- <p v-if="finishedTime" class="finished-at d-none d-md-block">
- <gl-icon name="calendar" class="gl-vertical-align-baseline!" :size="12" />
+ <p v-if="finishedTime" class="finished-at d-none d-md-block">
+ <gl-icon name="calendar" class="gl-vertical-align-baseline!" :size="12" />
- <time
- v-gl-tooltip
- :title="tooltipTitle(finishedTime)"
- data-placement="top"
- data-container="body"
- >
- {{ timeFormatted(finishedTime) }}
- </time>
- </p>
- </div>
+ <time
+ v-gl-tooltip
+ :title="tooltipTitle(finishedTime)"
+ data-placement="top"
+ data-container="body"
+ >
+ {{ timeFormatted(finishedTime) }}
+ </time>
+ </p>
</div>
</template>