Commit d2f77e36 authored by Markus Koller's avatar Markus Koller

Merge branch 'pb-pipelines-table-refactor-prep-work' into 'master'

Prep work for refactoring the pipelines table to use GlTable [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!54958
parents c2389dcf 6a360e9a
<script> <script>
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
components: { components: {
UserAvatarLink, UserAvatarLink,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
pipeline: { pipeline: {
type: Object, type: Object,
...@@ -15,11 +17,19 @@ export default { ...@@ -15,11 +17,19 @@ export default {
user() { user() {
return this.pipeline.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> </script>
<template> <template>
<div class="table-section section-10 d-none d-md-block pipeline-triggerer"> <div :class="classes">
<user-avatar-link <user-avatar-link
v-if="user" v-if="user"
:link-href="user.path" :link-href="user.path"
......
<script> <script>
import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui'; import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SCHEDULE_ORIGIN } from '../../constants'; import { SCHEDULE_ORIGIN } from '../../constants';
export default { export default {
...@@ -13,6 +14,7 @@ export default { ...@@ -13,6 +14,7 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
mixins: [glFeatureFlagMixin()],
inject: { inject: {
targetProjectFullPath: { targetProjectFullPath: {
default: '', default: '',
...@@ -47,11 +49,19 @@ export default { ...@@ -47,11 +49,19 @@ export default {
autoDevopsHelpPath() { autoDevopsHelpPath() {
return helpPagePath('topics/autodevops/index.md'); 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> </script>
<template> <template>
<div class="table-section section-10 d-none d-md-block pipeline-tags"> <div :class="classes">
<gl-link <gl-link
:href="pipeline.path" :href="pipeline.path"
data-testid="pipeline-url-link" data-testid="pipeline-url-link"
......
<script> <script>
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTable, GlTooltipDirective } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import PipelineStopModal from './pipeline_stop_modal.vue'; import PipelineStopModal from './pipeline_stop_modal.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue'; import PipelinesTableRowComponent from './pipelines_table_row.vue';
/**
* Pipelines Table Component.
*
* Given an array of objects, renders a table.
*/
export default { export default {
components: { components: {
GlTable,
PipelinesTableRowComponent, PipelinesTableRowComponent,
PipelineStopModal, PipelineStopModal,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
pipelines: { pipelines: {
type: Array, type: Array,
...@@ -45,6 +43,11 @@ export default { ...@@ -45,6 +43,11 @@ export default {
cancelingPipeline: null, cancelingPipeline: null,
}; };
}, },
computed: {
legacyTableClass() {
return !this.glFeatures.newPipelinesTable ? 'ci-table' : '';
},
},
watch: { watch: {
pipelines() { pipelines() {
this.cancelingPipeline = null; this.cancelingPipeline = null;
...@@ -70,37 +73,42 @@ export default { ...@@ -70,37 +73,42 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="ci-table"> <div :class="legacyTableClass">
<div class="gl-responsive-table-row table-row-header" role="row"> <div v-if="!glFeatures.newPipelinesTable" data-testid="ci-table">
<div class="table-section section-10 js-pipeline-status" role="rowheader"> <div class="gl-responsive-table-row table-row-header" role="row">
{{ s__('Pipeline|Status') }} <div class="table-section section-10 js-pipeline-status" role="rowheader">
</div> {{ s__('Pipeline|Status') }}
<div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader"> </div>
{{ s__('Pipeline|Pipeline') }} <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
</div> {{ s__('Pipeline|Pipeline') }}
<div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader"> </div>
{{ s__('Pipeline|Triggerer') }} <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
</div> {{ s__('Pipeline|Triggerer') }}
<div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader"> </div>
{{ s__('Pipeline|Commit') }} <div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
</div> {{ s__('Pipeline|Commit') }}
<div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader"> </div>
{{ s__('Pipeline|Stages') }} <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
</div> {{ s__('Pipeline|Stages') }}
<div class="table-section section-15" role="rowheader"></div> </div>
<div class="table-section section-20" role="rowheader"> <div class="table-section section-15" role="rowheader"></div>
<slot name="table-header-actions"></slot> <div class="table-section section-20" role="rowheader">
<slot name="table-header-actions"></slot>
</div>
</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> </div>
<pipelines-table-row-component
v-for="model in pipelines" <gl-table v-else />
:key="model.id"
:pipeline="model"
:pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown"
:view-type="viewType"
:canceling-pipeline="cancelingPipeline"
/>
<pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" /> <pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
</div> </div>
</template> </template>
...@@ -131,12 +131,6 @@ export default { ...@@ -131,12 +131,6 @@ export default {
commitTitle() { commitTitle() {
return this.pipeline?.commit?.title; return this.pipeline?.commit?.title;
}, },
pipelineDuration() {
return this.pipeline?.details?.duration ?? 0;
},
pipelineFinishedAt() {
return this.pipeline?.details?.finished_at ?? '';
},
pipelineStatus() { pipelineStatus() {
return this.pipeline?.details?.status ?? {}; return this.pipeline?.details?.status ?? {};
}, },
...@@ -231,11 +225,7 @@ export default { ...@@ -231,11 +225,7 @@ export default {
</div> </div>
</div> </div>
<pipelines-timeago <pipelines-timeago class="gl-text-right" :pipeline="pipeline" />
class="gl-text-right"
:duration="pipelineDuration"
:finished-time="pipelineFinishedAt"
/>
<div <div
v-if="displayPipelineActions" v-if="displayPipelineActions"
......
<script> <script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
export default { export default {
...@@ -7,23 +8,19 @@ export default { ...@@ -7,23 +8,19 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
components: { GlIcon }, components: { GlIcon },
mixins: [timeagoMixin], mixins: [timeagoMixin, glFeatureFlagMixin()],
props: { props: {
finishedTime: { pipeline: {
type: String, type: Object,
required: true,
},
duration: {
type: Number,
required: true, required: true,
}, },
}, },
computed: { computed: {
hasDuration() { duration() {
return this.duration > 0; return this.pipeline?.details?.duration;
}, },
hasFinishedTime() { finishedTime() {
return this.finishedTime !== ''; return this.pipeline?.details?.finished_at;
}, },
durationFormatted() { durationFormatted() {
const date = new Date(this.duration * 1000); const date = new Date(this.duration * 1000);
...@@ -45,20 +42,28 @@ export default { ...@@ -45,20 +42,28 @@ export default {
return `${hh}:${mm}:${ss}`; return `${hh}:${mm}:${ss}`;
}, },
legacySectionClass() {
return !this.glFeatures.newPipelinesTable ? 'table-section section-15' : '';
},
legacyTableMobileClass() {
return !this.glFeatures.newPipelinesTable ? 'table-mobile-content' : '';
},
}, },
}; };
</script> </script>
<template> <template>
<div class="table-section section-15"> <div :class="legacySectionClass">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div> <div v-if="!glFeatures.newPipelinesTable" class="table-mobile-header" role="rowheader">
<div class="table-mobile-content"> {{ s__('Pipeline|Duration') }}
<p v-if="hasDuration" class="duration"> </div>
<gl-icon name="timer" class="gl-vertical-align-baseline!" /> <div :class="legacyTableMobileClass">
<p v-if="duration" class="duration">
<gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
{{ durationFormatted }} {{ durationFormatted }}
</p> </p>
<p v-if="hasFinishedTime" class="finished-at d-none d-md-block"> <p v-if="finishedTime" class="finished-at d-none d-md-block">
<gl-icon name="calendar" class="gl-vertical-align-baseline!" /> <gl-icon name="calendar" class="gl-vertical-align-baseline!" :size="12" />
<time <time
v-gl-tooltip v-gl-tooltip
......
...@@ -18,6 +18,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -18,6 +18,7 @@ class Projects::PipelinesController < Projects::ApplicationController
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:jira_for_vulnerabilities, project, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:jira_for_vulnerabilities, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:new_pipelines_table, project, default_enabled: :yaml)
end end
before_action :ensure_pipeline, only: [:show] before_action :ensure_pipeline, only: [:show]
......
---
name: new_pipelines_table
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54958
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/322599
milestone: '13.10'
type: development
group: group::continuous integration
default_enabled: false
...@@ -26,6 +26,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', ...@@ -26,6 +26,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
end end
before do before do
stub_feature_flags(new_pipelines_table: false)
stub_application_setting(auto_devops_enabled: false) stub_application_setting(auto_devops_enabled: false)
stub_ci_pipeline_yaml_file(YAML.dump(config)) stub_ci_pipeline_yaml_file(YAML.dump(config))
project.add_maintainer(user) project.add_maintainer(user)
......
...@@ -14,6 +14,7 @@ RSpec.describe 'Pipelines', :js do ...@@ -14,6 +14,7 @@ RSpec.describe 'Pipelines', :js do
sign_in(user) sign_in(user)
stub_feature_flags(graphql_pipeline_details: false) stub_feature_flags(graphql_pipeline_details: false)
stub_feature_flags(graphql_pipeline_details_users: false) stub_feature_flags(graphql_pipeline_details_users: false)
stub_feature_flags(new_pipelines_table: false)
project.add_developer(user) project.add_developer(user)
project.update!(auto_devops_attributes: { enabled: false }) project.update!(auto_devops_attributes: { enabled: false })
......
import { GlTable } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
describe('Pipelines Table', () => { describe('Pipelines Table', () => {
...@@ -12,20 +14,28 @@ describe('Pipelines Table', () => { ...@@ -12,20 +14,28 @@ describe('Pipelines Table', () => {
viewType: 'root', viewType: 'root',
}; };
const createComponent = (props = defaultProps) => { const createComponent = (props = defaultProps, flagState = false) => {
wrapper = mount(PipelinesTable, { wrapper = extendedWrapper(
propsData: props, mount(PipelinesTable, {
}); propsData: props,
provide: {
glFeatures: {
newPipelinesTable: flagState,
},
},
}),
);
}; };
const findRows = () => wrapper.findAll('.commit.gl-responsive-table-row'); const findRows = () => wrapper.findAll('.commit.gl-responsive-table-row');
const findGlTable = () => wrapper.findComponent(GlTable);
const findLegacyTable = () => wrapper.findByTestId('ci-table');
preloadFixtures(jsonFixtureName); preloadFixtures(jsonFixtureName);
beforeEach(() => { beforeEach(() => {
const { pipelines } = getJSONFixture(jsonFixtureName); const { pipelines } = getJSONFixture(jsonFixtureName);
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null); pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
createComponent();
}); });
afterEach(() => { afterEach(() => {
...@@ -33,33 +43,50 @@ describe('Pipelines Table', () => { ...@@ -33,33 +43,50 @@ describe('Pipelines Table', () => {
wrapper = null; wrapper = null;
}); });
describe('table', () => { describe('table with feature flag off', () => {
it('should render a table', () => { describe('renders the table correctly', () => {
expect(wrapper.classes()).toContain('ci-table'); beforeEach(() => {
}); createComponent();
});
it('should render a table', () => {
expect(wrapper.classes()).toContain('ci-table');
});
it('should render table head with correct columns', () => { it('should render table head with correct columns', () => {
expect(wrapper.find('.table-section.js-pipeline-status').text()).toEqual('Status'); expect(wrapper.find('.table-section.js-pipeline-status').text()).toEqual('Status');
expect(wrapper.find('.table-section.js-pipeline-info').text()).toEqual('Pipeline'); expect(wrapper.find('.table-section.js-pipeline-info').text()).toEqual('Pipeline');
expect(wrapper.find('.table-section.js-pipeline-commit').text()).toEqual('Commit'); expect(wrapper.find('.table-section.js-pipeline-commit').text()).toEqual('Commit');
expect(wrapper.find('.table-section.js-pipeline-stages').text()).toEqual('Stages'); expect(wrapper.find('.table-section.js-pipeline-stages').text()).toEqual('Stages');
});
}); });
});
describe('without data', () => { describe('without data', () => {
it('should render an empty table', () => { it('should render an empty table', () => {
expect(findRows()).toHaveLength(0); createComponent();
expect(findRows()).toHaveLength(0);
});
});
describe('with data', () => {
it('should render rows', () => {
createComponent({ pipelines: [pipeline], viewType: 'root' });
expect(findRows()).toHaveLength(1);
});
}); });
}); });
describe('with data', () => { describe('table with feature flag on', () => {
it('should render rows', () => { it('displays new table', () => {
createComponent({ pipelines: [pipeline], viewType: 'root' }); createComponent(defaultProps, true);
expect(findRows()).toHaveLength(1); expect(findGlTable().exists()).toBe(true);
expect(findLegacyTable().exists()).toBe(false);
}); });
}); });
}); });
...@@ -8,7 +8,11 @@ describe('Timeago component', () => { ...@@ -8,7 +8,11 @@ describe('Timeago component', () => {
const createComponent = (props = {}) => { const createComponent = (props = {}) => {
wrapper = shallowMount(TimeAgo, { wrapper = shallowMount(TimeAgo, {
propsData: { propsData: {
...props, pipeline: {
details: {
...props,
},
},
}, },
data() { data() {
return { return {
...@@ -28,7 +32,7 @@ describe('Timeago component', () => { ...@@ -28,7 +32,7 @@ describe('Timeago component', () => {
describe('with duration', () => { describe('with duration', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ duration: 10, finishedTime: '' }); createComponent({ duration: 10, finished_at: '' });
}); });
it('should render duration and timer svg', () => { it('should render duration and timer svg', () => {
...@@ -41,7 +45,7 @@ describe('Timeago component', () => { ...@@ -41,7 +45,7 @@ describe('Timeago component', () => {
describe('without duration', () => { describe('without duration', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ duration: 0, finishedTime: '' }); createComponent({ duration: 0, finished_at: '' });
}); });
it('should not render duration and timer svg', () => { it('should not render duration and timer svg', () => {
...@@ -51,7 +55,7 @@ describe('Timeago component', () => { ...@@ -51,7 +55,7 @@ describe('Timeago component', () => {
describe('with finishedTime', () => { describe('with finishedTime', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ duration: 0, finishedTime: '2017-04-26T12:40:23.277Z' }); createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' });
}); });
it('should render time and calendar icon', () => { it('should render time and calendar icon', () => {
...@@ -66,7 +70,7 @@ describe('Timeago component', () => { ...@@ -66,7 +70,7 @@ describe('Timeago component', () => {
describe('without finishedTime', () => { describe('without finishedTime', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ duration: 0, finishedTime: '' }); createComponent({ duration: 0, finished_at: '' });
}); });
it('should not render time and calendar icon', () => { it('should not render time and calendar icon', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment