Skip to content
Snippets Groups Projects
Verified Commit c98a75c1 authored by Laura Montemayor's avatar Laura Montemayor :two: Committed by GitLab
Browse files

Merge branch 'bs-pipeline-table-loader-row' into 'master'

Add loading row to pipelines table

See merge request !163551



Merged-by: Laura Montemayor's avatarLaura Montemayor <lmontemayor@gitlab.com>
Approved-by: default avatarSunjung Park <spark@gitlab.com>
Approved-by: Laura Montemayor's avatarLaura Montemayor <lmontemayor@gitlab.com>
Co-authored-by: default avatarBriley Sandlin <bsandlin@gitlab.com>
parents 8056f6a7 e17bb9ac
No related branches found
No related tags found
1 merge request!163551Add loading row to pipelines table
Pipeline #1424820548 passed with warnings
<script>
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { GlSkeletonLoader, GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
......@@ -32,7 +33,10 @@ const HIDE_TD_ON_MOBILE = '!gl-hidden lg:!gl-table-cell';
*/
export default {
name: 'PipelinesTable',
cellHeight: 50,
components: {
GlSkeletonLoader,
GlTableLite,
LegacyPipelineMiniGraph,
PipelineFailedJobsWidget,
......@@ -51,15 +55,15 @@ export default {
},
},
props: {
pipelines: {
type: Array,
required: true,
},
updateGraphDropdown: {
isCreatingPipeline: {
type: Boolean,
required: false,
default: false,
},
pipelines: {
type: Array,
required: true,
},
pipelineIdType: {
type: String,
required: false,
......@@ -68,8 +72,16 @@ export default {
return value === PIPELINE_IID_KEY || value === PIPELINE_ID_KEY;
},
},
updateGraphDropdown: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isMobile() {
return ['md', 'sm', 'xs'].includes(GlBreakpointInstance.getBreakpointSize());
},
tableFields() {
return [
{
......@@ -112,13 +124,19 @@ export default {
return this.useFailedJobsWidget ? '!gl-pb-0 !gl-border-none' : 'pl-p-5!';
},
pipelinesWithDetails() {
let { pipelines } = this;
if (this.isCreatingPipeline) {
pipelines = [{ isLoading: true }, ...this.pipelines];
}
if (this.useFailedJobsWidget) {
return this.pipelines.map((p) => {
pipelines = pipelines.map((p) => {
return { ...p, _showDetails: true };
});
}
return this.pipelines;
return pipelines;
},
},
methods: {
......@@ -126,6 +144,9 @@ export default {
const downstream = pipeline.triggered;
return keepLatestDownstreamPipelines(downstream);
},
cellWidth(ref) {
return this.$refs[ref]?.offsetWidth;
},
getProjectPath(item) {
return cleanLeadingSeparator(item.project.full_path);
},
......@@ -144,6 +165,13 @@ export default {
onCancelPipeline(pipeline) {
this.$emit('cancel-pipeline', pipeline);
},
setLoaderPosition(ref) {
if (this.isMobile) {
return this.cellWidth(ref) / 2;
}
return 0;
},
trackPipelineMiniGraph() {
this.track('click_minigraph', { label: TRACKING_CATEGORIES.table });
},
......@@ -172,11 +200,30 @@ export default {
</template>
<template #cell(status)="{ item }">
<pipeline-status-badge :pipeline="item" />
<div v-if="item.isLoading" ref="status">
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('status')">
<rect height="30" rx="4" ry="4" :width="cellWidth('status')" />
</gl-skeleton-loader>
</div>
<pipeline-status-badge v-else :pipeline="item" />
</template>
<template #cell(pipeline)="{ item }">
<div v-if="item.isLoading" ref="pipeline">
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('pipeline')">
<rect height="14" rx="4" ry="4" :width="cellWidth('pipeline')" />
<rect
height="10"
rx="4"
ry="4"
:width="cellWidth('pipeline') / 2"
:x="setLoaderPosition('pipeline')"
y="20"
/>
</gl-skeleton-loader>
</div>
<pipeline-url
v-else
:pipeline="item"
:pipeline-id-type="pipelineIdType"
ref-color="gl-text-default"
......@@ -184,11 +231,22 @@ export default {
</template>
<template #cell(triggerer)="{ item }">
<pipeline-triggerer :pipeline="item" />
<div v-if="item.isLoading" ref="triggerer" class="gl-ml-3">
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('triggerer')">
<rect :height="34" rx="50" ry="50" :width="34" />
</gl-skeleton-loader>
</div>
<pipeline-triggerer v-else :pipeline="item" />
</template>
<template #cell(stages)="{ item }">
<div v-if="item.isLoading" ref="stages">
<gl-skeleton-loader :height="$options.cellHeight" :width="cellWidth('stages')">
<rect height="20" rx="10" ry="10" :width="cellWidth('stages')" />
</gl-skeleton-loader>
</div>
<legacy-pipeline-mini-graph
v-else
:downstream-pipelines="getDownstreamPipelines(item)"
:pipeline-path="item.path"
:stages="getStages(item)"
......@@ -200,6 +258,7 @@ export default {
<template #cell(actions)="{ item }">
<pipeline-operations
v-if="!item.isLoading"
:pipeline="item"
@cancel-pipeline="onCancelPipeline"
@refresh-pipelines-table="onRefreshPipelinesTable"
......@@ -209,7 +268,7 @@ export default {
<template #row-details="{ item }">
<pipeline-failed-jobs-widget
v-if="useFailedJobsWidget"
v-if="useFailedJobsWidget && !item.isLoading"
:failed-jobs-count="failedJobsCount(item)"
:is-pipeline-active="item.active"
:pipeline-iid="item.iid"
......
......@@ -296,10 +296,11 @@ export default {
</gl-button>
<pipelines-table
:is-creating-pipeline="state.isRunningMergeRequestPipeline"
:pipeline-id-type="$options.pipelineIdKey"
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:view-type="viewType"
:pipeline-id-type="$options.pipelineIdKey"
@cancel-pipeline="onCancelPipeline"
@refresh-pipelines-table="onRefreshPipelinesTable"
@retry-pipeline="onRetryPipeline"
......
import { GlTableLite } from '@gitlab/ui';
import { GlTableLite, GlSkeletonLoader } from '@gitlab/ui';
import fixture from 'test_fixtures/pipelines/pipelines.json';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
......@@ -57,6 +57,7 @@ describe('Pipelines Table', () => {
});
};
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findGlTableLite = () => wrapper.findComponent(GlTableLite);
const findCiIcon = () => wrapper.findComponent(CiIcon);
const findPipelineInfo = () => wrapper.findComponent(PipelineUrl);
......@@ -216,6 +217,36 @@ describe('Pipelines Table', () => {
});
});
});
describe('async pipeline creation', () => {
describe('when isCreatingPipeline is enabled', () => {
beforeEach(() => {
createComponent({ props: { isCreatingPipeline: true } });
});
it('Adds an additional loader row to the pipelines table', () => {
expect(findTableRows()).toHaveLength(pipelines.length + 1);
});
it('renders the skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(true);
});
});
describe('when isCreatingPipeline is disabled', () => {
beforeEach(() => {
createComponent();
});
it('does not add a loader row to the pipelines table', () => {
expect(findTableRows()).toHaveLength(pipelines.length);
});
it('does not render skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(false);
});
});
});
});
describe('events', () => {
......
......@@ -252,6 +252,14 @@ describe('Pipelines table in Commits and Merge requests', () => {
expect(findRunPipelineBtn().props('disabled')).toBe(false);
});
it('sets isCreatingPipeline to true in pipelines table', async () => {
expect(findPipelinesTable().props('isCreatingPipeline')).toBe(false);
await findRunPipelineBtn().trigger('click');
expect(findPipelinesTable().props('isCreatingPipeline')).toBe(true);
});
});
describe('when asyncMergeRequestPipelineCreation is disabled', () => {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment