Commit 6a360e9a authored by Payton Burdette's avatar Payton Burdette Committed by Markus Koller

Introduce feature flag

Add feature flag and edit
existing components that will
not be wrapped.
parent c3111e93
<script>
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
UserAvatarLink,
},
mixins: [glFeatureFlagMixin()],
props: {
pipeline: {
type: Object,
......@@ -15,11 +17,19 @@ 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="table-section section-10 d-none d-md-block pipeline-triggerer">
<div :class="classes">
<user-avatar-link
v-if="user"
:link-href="user.path"
......
<script>
import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SCHEDULE_ORIGIN } from '../../constants';
export default {
......@@ -13,6 +14,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagMixin()],
inject: {
targetProjectFullPath: {
default: '',
......@@ -47,11 +49,19 @@ 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="table-section section-10 d-none d-md-block pipeline-tags">
<div :class="classes">
<gl-link
:href="pipeline.path"
data-testid="pipeline-url-link"
......
<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 PipelineStopModal from './pipeline_stop_modal.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
/**
* Pipelines Table Component.
*
* Given an array of objects, renders a table.
*/
export default {
components: {
GlTable,
PipelinesTableRowComponent,
PipelineStopModal,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagMixin()],
props: {
pipelines: {
type: Array,
......@@ -45,6 +43,11 @@ export default {
cancelingPipeline: null,
};
},
computed: {
legacyTableClass() {
return !this.glFeatures.newPipelinesTable ? 'ci-table' : '';
},
},
watch: {
pipelines() {
this.cancelingPipeline = null;
......@@ -70,7 +73,8 @@ export default {
};
</script>
<template>
<div class="ci-table">
<div :class="legacyTableClass">
<div v-if="!glFeatures.newPipelinesTable" data-testid="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') }}
......@@ -101,6 +105,10 @@ export default {
:view-type="viewType"
:canceling-pipeline="cancelingPipeline"
/>
</div>
<gl-table v-else />
<pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
</div>
</template>
......@@ -131,12 +131,6 @@ export default {
commitTitle() {
return this.pipeline?.commit?.title;
},
pipelineDuration() {
return this.pipeline?.details?.duration ?? 0;
},
pipelineFinishedAt() {
return this.pipeline?.details?.finished_at ?? '';
},
pipelineStatus() {
return this.pipeline?.details?.status ?? {};
},
......@@ -231,11 +225,7 @@ export default {
</div>
</div>
<pipelines-timeago
class="gl-text-right"
:duration="pipelineDuration"
:finished-time="pipelineFinishedAt"
/>
<pipelines-timeago class="gl-text-right" :pipeline="pipeline" />
<div
v-if="displayPipelineActions"
......
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
......@@ -7,23 +8,19 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: { GlIcon },
mixins: [timeagoMixin],
mixins: [timeagoMixin, glFeatureFlagMixin()],
props: {
finishedTime: {
type: String,
required: true,
},
duration: {
type: Number,
pipeline: {
type: Object,
required: true,
},
},
computed: {
hasDuration() {
return this.duration > 0;
duration() {
return this.pipeline?.details?.duration;
},
hasFinishedTime() {
return this.finishedTime !== '';
finishedTime() {
return this.pipeline?.details?.finished_at;
},
durationFormatted() {
const date = new Date(this.duration * 1000);
......@@ -45,20 +42,28 @@ export default {
return `${hh}:${mm}:${ss}`;
},
legacySectionClass() {
return !this.glFeatures.newPipelinesTable ? 'table-section section-15' : '';
},
legacyTableMobileClass() {
return !this.glFeatures.newPipelinesTable ? 'table-mobile-content' : '';
},
},
};
</script>
<template>
<div class="table-section section-15">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
<div class="table-mobile-content">
<p v-if="hasDuration" class="duration">
<gl-icon name="timer" class="gl-vertical-align-baseline!" />
<div :class="legacySectionClass">
<div v-if="!glFeatures.newPipelinesTable" class="table-mobile-header" role="rowheader">
{{ s__('Pipeline|Duration') }}
</div>
<div :class="legacyTableMobileClass">
<p v-if="duration" class="duration">
<gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
{{ durationFormatted }}
</p>
<p v-if="hasFinishedTime" class="finished-at d-none d-md-block">
<gl-icon name="calendar" class="gl-vertical-align-baseline!" />
<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
......
......@@ -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_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(:new_pipelines_table, project, default_enabled: :yaml)
end
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',
end
before do
stub_feature_flags(new_pipelines_table: false)
stub_application_setting(auto_devops_enabled: false)
stub_ci_pipeline_yaml_file(YAML.dump(config))
project.add_maintainer(user)
......
......@@ -14,6 +14,7 @@ RSpec.describe 'Pipelines', :js do
sign_in(user)
stub_feature_flags(graphql_pipeline_details: false)
stub_feature_flags(graphql_pipeline_details_users: false)
stub_feature_flags(new_pipelines_table: false)
project.add_developer(user)
project.update!(auto_devops_attributes: { enabled: false })
......
import { GlTable } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
describe('Pipelines Table', () => {
......@@ -12,20 +14,28 @@ describe('Pipelines Table', () => {
viewType: 'root',
};
const createComponent = (props = defaultProps) => {
wrapper = mount(PipelinesTable, {
const createComponent = (props = defaultProps, flagState = false) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: props,
});
provide: {
glFeatures: {
newPipelinesTable: flagState,
},
},
}),
);
};
const findRows = () => wrapper.findAll('.commit.gl-responsive-table-row');
const findGlTable = () => wrapper.findComponent(GlTable);
const findLegacyTable = () => wrapper.findByTestId('ci-table');
preloadFixtures(jsonFixtureName);
beforeEach(() => {
const { pipelines } = getJSONFixture(jsonFixtureName);
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
createComponent();
});
afterEach(() => {
......@@ -33,7 +43,12 @@ describe('Pipelines Table', () => {
wrapper = null;
});
describe('table', () => {
describe('table with feature flag off', () => {
describe('renders the table correctly', () => {
beforeEach(() => {
createComponent();
});
it('should render a table', () => {
expect(wrapper.classes()).toContain('ci-table');
});
......@@ -51,6 +66,8 @@ describe('Pipelines Table', () => {
describe('without data', () => {
it('should render an empty table', () => {
createComponent();
expect(findRows()).toHaveLength(0);
});
});
......@@ -62,4 +79,14 @@ describe('Pipelines Table', () => {
expect(findRows()).toHaveLength(1);
});
});
});
describe('table with feature flag on', () => {
it('displays new table', () => {
createComponent(defaultProps, true);
expect(findGlTable().exists()).toBe(true);
expect(findLegacyTable().exists()).toBe(false);
});
});
});
......@@ -8,8 +8,12 @@ describe('Timeago component', () => {
const createComponent = (props = {}) => {
wrapper = shallowMount(TimeAgo, {
propsData: {
pipeline: {
details: {
...props,
},
},
},
data() {
return {
iconTimerSvg: `<svg></svg>`,
......@@ -28,7 +32,7 @@ describe('Timeago component', () => {
describe('with duration', () => {
beforeEach(() => {
createComponent({ duration: 10, finishedTime: '' });
createComponent({ duration: 10, finished_at: '' });
});
it('should render duration and timer svg', () => {
......@@ -41,7 +45,7 @@ describe('Timeago component', () => {
describe('without duration', () => {
beforeEach(() => {
createComponent({ duration: 0, finishedTime: '' });
createComponent({ duration: 0, finished_at: '' });
});
it('should not render duration and timer svg', () => {
......@@ -51,7 +55,7 @@ describe('Timeago component', () => {
describe('with finishedTime', () => {
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', () => {
......@@ -66,7 +70,7 @@ describe('Timeago component', () => {
describe('without finishedTime', () => {
beforeEach(() => {
createComponent({ duration: 0, finishedTime: '' });
createComponent({ duration: 0, finished_at: '' });
});
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