Commit 23401de0 authored by Brandon Labuschagne's avatar Brandon Labuschagne

Merge branch 'jivanvl-reestructure-pipelines-page' into 'master'

Rearrange pipelines page

See merge request gitlab-org/gitlab!72545
parents 503ae0a4 3a64b33b
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
v-if="user" v-if="user"
:link-href="user.path" :link-href="user.path"
:img-src="user.avatar_url" :img-src="user.avatar_url"
:img-size="26" :img-size="32"
:tooltip-text="user.name" :tooltip-text="user.name"
class="gl-ml-3 js-pipeline-url-user" class="gl-ml-3 js-pipeline-url-user"
/> />
......
<script> <script>
import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui'; import { GlIcon, GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
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 glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { SCHEDULE_ORIGIN } from '../../constants'; import { SCHEDULE_ORIGIN } from '../../constants';
export default { export default {
components: { components: {
GlIcon,
GlLink, GlLink,
GlPopover, GlPopover,
GlSprintf, GlSprintf,
GlBadge, GlBadge,
TooltipOnTruncate,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -33,11 +37,12 @@ export default { ...@@ -33,11 +37,12 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
viewType: {
type: String,
required: true,
},
}, },
computed: { computed: {
user() {
return this.pipeline.user;
},
isScheduled() { isScheduled() {
return this.pipeline.source === SCHEDULE_ORIGIN; return this.pipeline.source === SCHEDULE_ORIGIN;
}, },
...@@ -53,12 +58,144 @@ export default { ...@@ -53,12 +58,144 @@ export default {
autoDevopsHelpPath() { autoDevopsHelpPath() {
return helpPagePath('topics/autodevops/index.md'); return helpPagePath('topics/autodevops/index.md');
}, },
mergeRequestRef() {
return this.pipeline?.merge_request;
},
commitRef() {
return this.pipeline?.ref;
},
commitTag() {
return this.commitRef?.tag;
},
commitUrl() {
return this.pipeline?.commit?.commit_path;
},
commitShortSha() {
return this.pipeline?.commit?.short_id;
},
refUrl() {
return this.commitRef?.ref_url || this.commitRef?.path;
},
tooltipTitle() {
return this.mergeRequestRef?.title || this.commitRef?.name;
},
commitAuthor() {
let commitAuthorInformation;
const pipelineCommit = this.pipeline?.commit;
const pipelineCommitAuthor = pipelineCommit?.author;
if (!pipelineCommit) {
return null;
}
// 1. person who is an author of a commit might be a GitLab user
if (pipelineCommitAuthor) {
// 2. if person who is an author of a commit is a GitLab user
// they can have a GitLab avatar
if (pipelineCommitAuthor?.avatar_url) {
commitAuthorInformation = pipelineCommitAuthor;
// 3. If GitLab user does not have avatar, they might have a Gravatar
} else if (pipelineCommit.author_gravatar_url) {
commitAuthorInformation = {
...pipelineCommitAuthor,
avatar_url: pipelineCommit.author_gravatar_url,
};
}
// 4. If committer is not a GitLab User, they can have a Gravatar
} else {
commitAuthorInformation = {
avatar_url: pipelineCommit.author_gravatar_url,
path: `mailto:${pipelineCommit.author_email}`,
username: pipelineCommit.author_name,
};
}
return commitAuthorInformation;
},
commitIcon() {
let name = '';
if (this.commitTag) {
name = 'tag';
} else if (this.mergeRequestRef) {
name = 'git-merge';
} else {
name = 'branch';
}
return name;
},
commitTitle() {
return this.pipeline?.commit?.title;
},
hasAuthor() {
return (
this.commitAuthor?.avatar_url && this.commitAuthor?.path && this.commitAuthor?.username
);
},
userImageAltDescription() {
return this.commitAuthor?.username
? sprintf(__("%{username}'s avatar"), { username: this.commitAuthor.username })
: null;
},
rearrangePipelinesTable() {
return this.glFeatures?.rearrangePipelinesTable;
},
}, },
}; };
</script> </script>
<template> <template>
<div class="pipeline-tags" data-testid="pipeline-url-table-cell"> <div class="pipeline-tags" data-testid="pipeline-url-table-cell">
<template v-if="rearrangePipelinesTable">
<div class="commit-title gl-mb-2" data-testid="commit-title-container">
<span v-if="commitTitle" class="gl-display-flex">
<tooltip-on-truncate :title="commitTitle" class="flex-truncate-child gl-flex-grow-1">
<gl-link
:href="commitUrl"
class="commit-row-message gl-text-gray-900"
data-testid="commit-title"
>{{ commitTitle }}</gl-link
>
</tooltip-on-truncate>
</span>
<span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
</div>
<div class="gl-mb-2">
<gl-link
:href="pipeline.path"
class="gl-text-decoration-underline gl-text-blue-600!"
data-testid="pipeline-url-link"
data-qa-selector="pipeline_url_link"
>
#{{ pipeline[pipelineKey] }}
</gl-link>
<!--Commit row-->
<div class="icon-container gl-display-inline-block">
<gl-icon :name="commitIcon" />
</div>
<tooltip-on-truncate :title="tooltipTitle" truncate-target="child" placement="top">
<gl-link
v-if="mergeRequestRef"
:href="mergeRequestRef.path"
class="ref-name"
data-testid="merge-request-ref"
>{{ mergeRequestRef.iid }}</gl-link
>
<gl-link v-else :href="refUrl" class="ref-name" data-testid="commit-ref-name">{{
commitRef.name
}}</gl-link>
</tooltip-on-truncate>
<gl-icon name="commit" class="commit-icon" />
<gl-link :href="commitUrl" class="commit-sha mr-0" data-testid="commit-short-sha">{{
commitShortSha
}}</gl-link>
<!--End of commit row-->
</div>
</template>
<gl-link <gl-link
v-if="!rearrangePipelinesTable"
:href="pipeline.path" :href="pipeline.path"
class="gl-text-decoration-underline" class="gl-text-decoration-underline"
data-testid="pipeline-url-link" data-testid="pipeline-url-link"
...@@ -66,7 +203,7 @@ export default { ...@@ -66,7 +203,7 @@ export default {
> >
#{{ pipeline[pipelineKey] }} #{{ pipeline[pipelineKey] }}
</gl-link> </gl-link>
<div class="label-container"> <div class="label-container gl-mt-1">
<gl-badge <gl-badge
v-if="isScheduled" v-if="isScheduled"
v-gl-tooltip v-gl-tooltip
......
...@@ -3,12 +3,16 @@ import CodeQualityWalkthrough from '~/code_quality_walkthrough/components/step.v ...@@ -3,12 +3,16 @@ import CodeQualityWalkthrough from '~/code_quality_walkthrough/components/step.v
import { PIPELINE_STATUSES } from '~/code_quality_walkthrough/constants'; import { PIPELINE_STATUSES } from '~/code_quality_walkthrough/constants';
import { CHILD_VIEW } from '~/pipelines/constants'; import { CHILD_VIEW } from '~/pipelines/constants';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PipelinesTimeago from './time_ago.vue';
export default { export default {
components: { components: {
CodeQualityWalkthrough, CodeQualityWalkthrough,
CiBadge, CiBadge,
PipelinesTimeago,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
pipeline: { pipeline: {
type: Object, type: Object,
...@@ -40,6 +44,9 @@ export default { ...@@ -40,6 +44,9 @@ export default {
codeQualityBuildPath() { codeQualityBuildPath() {
return this.pipeline?.details?.code_quality_build_path; return this.pipeline?.details?.code_quality_build_path;
}, },
rearrangePipelinesTable() {
return this.glFeatures?.rearrangePipelinesTable;
},
}, },
}; };
</script> </script>
...@@ -48,11 +55,13 @@ export default { ...@@ -48,11 +55,13 @@ export default {
<div> <div>
<ci-badge <ci-badge
id="js-code-quality-walkthrough" id="js-code-quality-walkthrough"
class="gl-mb-3"
:status="pipelineStatus" :status="pipelineStatus"
:show-text="!isChildView" :show-text="!isChildView"
:icon-classes="'gl-vertical-align-middle!'" :icon-classes="'gl-vertical-align-middle!'"
data-qa-selector="pipeline_commit_status" data-qa-selector="pipeline_commit_status"
/> />
<pipelines-timeago v-if="rearrangePipelinesTable" class="gl-mt-3" :pipeline="pipeline" />
<code-quality-walkthrough <code-quality-walkthrough
v-if="shouldRenderCodeQualityWalkthrough" v-if="shouldRenderCodeQualityWalkthrough"
:step="codeQualityStep" :step="codeQualityStep"
......
<script> <script>
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui'; import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import PipelineMiniGraph from './pipeline_mini_graph.vue'; import PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineOperations from './pipeline_operations.vue'; import PipelineOperations from './pipeline_operations.vue';
...@@ -33,6 +34,7 @@ export default { ...@@ -33,6 +34,7 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
pipelines: { pipelines: {
type: Array, type: Array,
...@@ -72,16 +74,18 @@ export default { ...@@ -72,16 +74,18 @@ export default {
key: 'status', key: 'status',
label: s__('Pipeline|Status'), label: s__('Pipeline|Status'),
thClass: DEFAULT_TH_CLASSES, thClass: DEFAULT_TH_CLASSES,
columnClass: 'gl-w-10p', columnClass: this.rearrangePipelinesTable ? 'gl-w-15p' : 'gl-w-10p',
tdClass: DEFAULT_TD_CLASS, tdClass: DEFAULT_TD_CLASS,
thAttr: { 'data-testid': 'status-th' }, thAttr: { 'data-testid': 'status-th' },
}, },
{ {
key: 'pipeline', key: 'pipeline',
label: this.pipelineKeyOption.label, label: this.rearrangePipelinesTable ? __('Pipeline') : this.pipelineKeyOption.label,
thClass: DEFAULT_TH_CLASSES, thClass: DEFAULT_TH_CLASSES,
tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`, tdClass: this.rearrangePipelinesTable
columnClass: 'gl-w-10p', ? `${DEFAULT_TD_CLASS}`
: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: this.rearrangePipelinesTable ? 'gl-w-30p' : 'gl-w-10p',
thAttr: { 'data-testid': 'pipeline-th' }, thAttr: { 'data-testid': 'pipeline-th' },
}, },
{ {
...@@ -113,7 +117,7 @@ export default { ...@@ -113,7 +117,7 @@ export default {
label: s__('Pipeline|Duration'), label: s__('Pipeline|Duration'),
thClass: DEFAULT_TH_CLASSES, thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS, tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-15p', columnClass: this.rearrangePipelinesTable ? 'gl-w-5p' : 'gl-w-15p',
thAttr: { 'data-testid': 'timeago-th' }, thAttr: { 'data-testid': 'timeago-th' },
}, },
{ {
...@@ -124,7 +128,13 @@ export default { ...@@ -124,7 +128,13 @@ export default {
thAttr: { 'data-testid': 'actions-th' }, thAttr: { 'data-testid': 'actions-th' },
}, },
]; ];
return fields;
return !this.rearrangePipelinesTable
? fields
: fields.filter((field) => !['commit', 'timeago'].includes(field.key));
},
rearrangePipelinesTable() {
return this.glFeatures?.rearrangePipelinesTable;
}, },
}, },
watch: { watch: {
...@@ -182,6 +192,7 @@ export default { ...@@ -182,6 +192,7 @@ export default {
:pipeline="item" :pipeline="item"
:pipeline-schedule-url="pipelineScheduleUrl" :pipeline-schedule-url="pipelineScheduleUrl"
:pipeline-key="pipelineKeyOption.key" :pipeline-key="pipelineKeyOption.key"
:view-type="viewType"
/> />
</template> </template>
......
...@@ -54,11 +54,14 @@ export default { ...@@ -54,11 +54,14 @@ export default {
showSkipped() { showSkipped() {
return !this.duration && !this.finishedTime && this.skipped; return !this.duration && !this.finishedTime && this.skipped;
}, },
shouldDisplayAsBlock() {
return this.glFeatures?.rearrangePipelinesTable;
},
}, },
}; };
</script> </script>
<template> <template>
<div> <div class="{ 'gl-display-block': shouldDisplayAsBlock }">
<span v-if="showInProgress" data-testid="pipeline-in-progress"> <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-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" />
<gl-icon <gl-icon
......
...@@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml) push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml) push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml) push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
# Usage data feature flags # Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml) push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml) push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
......
...@@ -13,6 +13,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -13,6 +13,9 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables] before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts] before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
before_action do
push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
end
before_action do before_action do
push_frontend_feature_flag(:jobs_tab_vue, @project, default_enabled: :yaml) push_frontend_feature_flag(:jobs_tab_vue, @project, default_enabled: :yaml)
......
---
name: rearrange_pipelines_table
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72545
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343286
milestone: '14.8'
type: development
group: group::pipeline execution
default_enabled: false
...@@ -26,11 +26,11 @@ module QA ...@@ -26,11 +26,11 @@ module QA
end end
def wait_for_latest_pipeline_succeeded def wait_for_latest_pipeline_succeeded
wait_for_latest_pipeline_status { has_text?('passed') } wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") }
end end
def wait_for_latest_pipeline_completed def wait_for_latest_pipeline_completed
wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') } wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") || has_selector?(".ci-status-icon-failed") }
end end
def wait_for_latest_pipeline_status def wait_for_latest_pipeline_status
......
...@@ -125,6 +125,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do ...@@ -125,6 +125,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
before do before do
stub_feature_flags(ci_disallow_to_create_merge_request_pipelines_in_target_project: false) stub_feature_flags(ci_disallow_to_create_merge_request_pipelines_in_target_project: false)
stub_feature_flags(rearrange_pipelines_table: false)
end end
it 'creates a pipeline in the parent project when user proceeds with the warning' do it 'creates a pipeline in the parent project when user proceeds with the warning' do
......
...@@ -159,7 +159,7 @@ RSpec.describe 'Pipelines', :js do ...@@ -159,7 +159,7 @@ RSpec.describe 'Pipelines', :js do
end end
end end
context 'when pipeline is detached merge request pipeline' do context 'when pipeline is detached merge request pipeline, with rearrange_pipelines_table feature flag turned off' do
let(:merge_request) do let(:merge_request) do
create(:merge_request, create(:merge_request,
:with_detached_merge_request_pipeline, :with_detached_merge_request_pipeline,
...@@ -172,6 +172,8 @@ RSpec.describe 'Pipelines', :js do ...@@ -172,6 +172,8 @@ RSpec.describe 'Pipelines', :js do
let(:target_project) { project } let(:target_project) { project }
before do before do
stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(source_project) visit project_pipelines_path(source_project)
end end
...@@ -201,7 +203,47 @@ RSpec.describe 'Pipelines', :js do ...@@ -201,7 +203,47 @@ RSpec.describe 'Pipelines', :js do
end end
end end
context 'when pipeline is merge request pipeline' do context 'when pipeline is detached merge request pipeline, with rearrange_pipelines_table feature flag turned on' do
let(:merge_request) do
create(:merge_request,
:with_detached_merge_request_pipeline,
source_project: source_project,
target_project: target_project)
end
let!(:pipeline) { merge_request.all_pipelines.first }
let(:source_project) { project }
let(:target_project) { project }
before do
stub_feature_flags(rearrange_pipelines_table: true)
visit project_pipelines_path(source_project)
end
shared_examples_for 'detached merge request pipeline' do
it 'shows pipeline information without pipeline ref', :sidekiq_might_not_need_inline do
within '.pipeline-tags' do
expect(page).to have_content('detached')
expect(page).to have_link(merge_request.iid,
href: project_merge_request_path(project, merge_request))
expect(page).not_to have_link(pipeline.ref)
end
end
end
it_behaves_like 'detached merge request pipeline'
context 'when source project is a forked project' do
let(:source_project) { fork_project(project, user, repository: true) }
it_behaves_like 'detached merge request pipeline'
end
end
context 'when pipeline is merge request pipeline, with rearrange_pipelines_table feature flag turned off' do
let(:merge_request) do let(:merge_request) do
create(:merge_request, create(:merge_request,
:with_merge_request_pipeline, :with_merge_request_pipeline,
...@@ -215,6 +257,8 @@ RSpec.describe 'Pipelines', :js do ...@@ -215,6 +257,8 @@ RSpec.describe 'Pipelines', :js do
let(:target_project) { project } let(:target_project) { project }
before do before do
stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(source_project) visit project_pipelines_path(source_project)
end end
...@@ -244,6 +288,47 @@ RSpec.describe 'Pipelines', :js do ...@@ -244,6 +288,47 @@ RSpec.describe 'Pipelines', :js do
end end
end end
context 'when pipeline is merge request pipeline, with rearrange_pipelines_table feature flag turned on' do
let(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
source_project: source_project,
target_project: target_project,
merge_sha: target_project.commit.sha)
end
let!(:pipeline) { merge_request.all_pipelines.first }
let(:source_project) { project }
let(:target_project) { project }
before do
stub_feature_flags(rearrange_pipelines_table: true)
visit project_pipelines_path(source_project)
end
shared_examples_for 'Correct merge request pipeline information' do
it 'does not show detached tag for the pipeline, and shows the link of the merge request, and does not show the ref of the pipeline', :sidekiq_might_not_need_inline do
within '.pipeline-tags' do
expect(page).not_to have_content('detached')
expect(page).to have_link(merge_request.iid,
href: project_merge_request_path(project, merge_request))
expect(page).not_to have_link(pipeline.ref)
end
end
end
it_behaves_like 'Correct merge request pipeline information'
context 'when source project is a forked project' do
let(:source_project) { fork_project(project, user, repository: true) }
it_behaves_like 'Correct merge request pipeline information'
end
end
context 'when pipeline has configuration errors' do context 'when pipeline has configuration errors' do
let(:pipeline) do let(:pipeline) do
create(:ci_pipeline, :invalid, project: project) create(:ci_pipeline, :invalid, project: project)
...@@ -587,6 +672,7 @@ RSpec.describe 'Pipelines', :js do ...@@ -587,6 +672,7 @@ RSpec.describe 'Pipelines', :js do
context 'with pipeline key selection' do context 'with pipeline key selection' do
before do before do
stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(project) visit project_pipelines_path(project)
wait_for_requests wait_for_requests
end end
...@@ -604,6 +690,27 @@ RSpec.describe 'Pipelines', :js do ...@@ -604,6 +690,27 @@ RSpec.describe 'Pipelines', :js do
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}" expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
end end
end end
context 'with pipeline key selection and rearrange_pipelines_table ff on' do
before do
stub_feature_flags(rearrange_pipelines_table: true)
visit project_pipelines_path(project)
wait_for_requests
end
it 'changes the Pipeline ID column for Pipeline IID' do
page.find('[data-testid="pipeline-key-dropdown"]').click
within '.gl-new-dropdown-contents' do
dropdown_options = page.find_all '.gl-new-dropdown-item'
dropdown_options[1].click
end
expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
end
end
end end
describe 'GET /:project/-/pipelines/show' do describe 'GET /:project/-/pipelines/show' do
......
...@@ -634,3 +634,80 @@ export const mockPipelineJobsQueryResponse = { ...@@ -634,3 +634,80 @@ export const mockPipelineJobsQueryResponse = {
}, },
}, },
}; };
export const mockPipeline = (projectPath) => {
return {
pipeline: {
id: 1,
user: {
id: 1,
name: 'Administrator',
username: 'root',
state: 'active',
avatar_url: '',
web_url: 'http://0.0.0.0:3000/root',
show_status: false,
path: '/root',
},
active: false,
source: 'merge_request_event',
created_at: '2021-10-19T21:17:38.698Z',
updated_at: '2021-10-21T18:00:42.758Z',
path: 'foo',
flags: {},
merge_request: {
iid: 1,
path: `/${projectPath}/1`,
title: 'commit',
source_branch: 'test-commit-name',
source_branch_path: `/${projectPath}`,
target_branch: 'main',
target_branch_path: `/${projectPath}/-/commit/main`,
},
ref: {
name: 'refs/merge-requests/1/head',
path: `/${projectPath}/-/commits/refs/merge-requests/1/head`,
tag: false,
branch: false,
merge_request: true,
},
commit: {
id: 'fd6df5b3229e213c97d308844a6f3e7fd71e8f8c',
short_id: 'fd6df5b3',
created_at: '2021-10-19T21:17:12.000+00:00',
parent_ids: ['7147906b84306e83cb3fec6582a25390b75713c6'],
title: 'Commit',
message: 'Commit',
author_name: 'Administrator',
author_email: 'admin@example.com',
authored_date: '2021-10-19T21:17:12.000+00:00',
committer_name: 'Administrator',
committer_email: 'admin@example.com',
committed_date: '2021-10-19T21:17:12.000+00:00',
trailers: {},
web_url: '',
author: {
id: 1,
name: 'Administrator',
username: 'root',
state: 'active',
avatar_url: '',
web_url: '',
show_status: false,
path: '/root',
},
author_gravatar_url: '',
commit_url: `/${projectPath}/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
commit_path: `/${projectPath}/commit/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
},
project: {
full_path: `/${projectPath}`,
},
triggered_by: null,
triggered: [],
},
pipelineScheduleUrl: 'foo',
pipelineKey: 'id',
viewType: 'root',
};
};
import { shallowMount } from '@vue/test-utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue'; import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
import { mockPipeline } from './mock_data';
const projectPath = 'test/test'; const projectPath = 'test/test';
describe('Pipeline Url Component', () => { describe('Pipeline Url Component', () => {
let wrapper; let wrapper;
const findTableCell = () => wrapper.find('[data-testid="pipeline-url-table-cell"]'); const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]'); const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]'); const findScheduledTag = () => wrapper.findByTestId('pipeline-url-scheduled');
const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]'); const findLatestTag = () => wrapper.findByTestId('pipeline-url-latest');
const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]'); const findYamlTag = () => wrapper.findByTestId('pipeline-url-yaml');
const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]'); const findFailureTag = () => wrapper.findByTestId('pipeline-url-failure');
const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]'); const findAutoDevopsTag = () => wrapper.findByTestId('pipeline-url-autodevops');
const findAutoDevopsTagLink = () => wrapper.find('[data-testid="pipeline-url-autodevops-link"]'); const findAutoDevopsTagLink = () => wrapper.findByTestId('pipeline-url-autodevops-link');
const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]'); const findStuckTag = () => wrapper.findByTestId('pipeline-url-stuck');
const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]'); const findDetachedTag = () => wrapper.findByTestId('pipeline-url-detached');
const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]'); const findForkTag = () => wrapper.findByTestId('pipeline-url-fork');
const findTrainTag = () => wrapper.find('[data-testid="pipeline-url-train"]'); const findTrainTag = () => wrapper.findByTestId('pipeline-url-train');
const findRefName = () => wrapper.findByTestId('merge-request-ref');
const defaultProps = { const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
pipeline: {
id: 1, const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
path: 'foo', const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
project: { full_path: `/${projectPath}` },
flags: {}, const defaultProps = mockPipeline(projectPath);
},
pipelineScheduleUrl: 'foo', const createComponent = (props, rearrangePipelinesTable = false) => {
pipelineKey: 'id', wrapper = shallowMountExtended(PipelineUrlComponent, {
};
const createComponent = (props) => {
wrapper = shallowMount(PipelineUrlComponent, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
provide: { provide: {
targetProjectFullPath: projectPath, targetProjectFullPath: projectPath,
glFeatures: {
rearrangePipelinesTable,
},
}, },
}); });
}; };
...@@ -45,158 +45,169 @@ describe('Pipeline Url Component', () => { ...@@ -45,158 +45,169 @@ describe('Pipeline Url Component', () => {
wrapper = null; wrapper = null;
}); });
it('should render pipeline url table cell', () => { describe('with the rearrangePipelinesTable feature flag turned off', () => {
createComponent(); it('should render pipeline url table cell', () => {
createComponent();
expect(findTableCell().exists()).toBe(true); expect(findTableCell().exists()).toBe(true);
}); });
it('should render a link the provided path and id', () => { it('should render a link the provided path and id', () => {
createComponent(); createComponent();
expect(findPipelineUrlLink().attributes('href')).toBe('foo'); expect(findPipelineUrlLink().attributes('href')).toBe('foo');
expect(findPipelineUrlLink().text()).toBe('#1'); expect(findPipelineUrlLink().text()).toBe('#1');
}); });
it('should not render tags when flags are not set', () => { it('should not render tags when flags are not set', () => {
createComponent(); createComponent();
expect(findStuckTag().exists()).toBe(false); expect(findStuckTag().exists()).toBe(false);
expect(findLatestTag().exists()).toBe(false); expect(findLatestTag().exists()).toBe(false);
expect(findYamlTag().exists()).toBe(false); expect(findYamlTag().exists()).toBe(false);
expect(findAutoDevopsTag().exists()).toBe(false); expect(findAutoDevopsTag().exists()).toBe(false);
expect(findFailureTag().exists()).toBe(false); expect(findFailureTag().exists()).toBe(false);
expect(findScheduledTag().exists()).toBe(false); expect(findScheduledTag().exists()).toBe(false);
expect(findForkTag().exists()).toBe(false); expect(findForkTag().exists()).toBe(false);
expect(findTrainTag().exists()).toBe(false); expect(findTrainTag().exists()).toBe(false);
}); });
it('should render the stuck tag when flag is provided', () => { it('should render the stuck tag when flag is provided', () => {
createComponent({ const stuckPipeline = defaultProps.pipeline;
pipeline: { stuckPipeline.flags.stuck = true;
flags: {
stuck: true, createComponent({
}, ...stuckPipeline.pipeline,
}, });
expect(findStuckTag().text()).toContain('stuck');
}); });
expect(findStuckTag().text()).toContain('stuck'); it('should render latest tag when flag is provided', () => {
}); const latestPipeline = defaultProps.pipeline;
latestPipeline.flags.latest = true;
it('should render latest tag when flag is provided', () => { createComponent({
createComponent({ ...latestPipeline,
pipeline: { });
flags: {
latest: true, expect(findLatestTag().text()).toContain('latest');
},
},
}); });
expect(findLatestTag().text()).toContain('latest'); it('should render a yaml badge when it is invalid', () => {
}); const yamlPipeline = defaultProps.pipeline;
yamlPipeline.flags.yaml_errors = true;
it('should render a yaml badge when it is invalid', () => { createComponent({
createComponent({ ...yamlPipeline,
pipeline: { });
flags: {
yaml_errors: true, expect(findYamlTag().text()).toContain('yaml invalid');
},
},
}); });
expect(findYamlTag().text()).toContain('yaml invalid'); it('should render an autodevops badge when flag is provided', () => {
}); const autoDevopsPipeline = defaultProps.pipeline;
autoDevopsPipeline.flags.auto_devops = true;
it('should render an autodevops badge when flag is provided', () => { createComponent({
createComponent({ ...autoDevopsPipeline,
pipeline: { });
...defaultProps.pipeline,
flags: { expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
auto_devops: true,
}, expect(findAutoDevopsTagLink().attributes()).toMatchObject({
}, href: '/help/topics/autodevops/index.md',
target: '_blank',
});
}); });
expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps'); it('should render a detached badge when flag is provided', () => {
const detachedMRPipeline = defaultProps.pipeline;
detachedMRPipeline.flags.detached_merge_request_pipeline = true;
expect(findAutoDevopsTagLink().attributes()).toMatchObject({ createComponent({
href: '/help/topics/autodevops/index.md', ...detachedMRPipeline,
target: '_blank', });
expect(findDetachedTag().text()).toContain('detached');
}); });
});
it('should render a detached badge when flag is provided', () => { it('should render error badge when pipeline has a failure reason set', () => {
createComponent({ const failedPipeline = defaultProps.pipeline;
pipeline: { failedPipeline.flags.failure_reason = true;
flags: { failedPipeline.failure_reason = 'some reason';
detached_merge_request_pipeline: true,
}, createComponent({
}, ...failedPipeline,
});
expect(findFailureTag().text()).toContain('error');
expect(findFailureTag().attributes('title')).toContain('some reason');
}); });
expect(findDetachedTag().text()).toContain('detached'); it('should render scheduled badge when pipeline was triggered by a schedule', () => {
}); const scheduledPipeline = defaultProps.pipeline;
scheduledPipeline.source = 'schedule';
it('should render error badge when pipeline has a failure reason set', () => { createComponent({
createComponent({ ...scheduledPipeline,
pipeline: { });
flags: {
failure_reason: true, expect(findScheduledTag().exists()).toBe(true);
}, expect(findScheduledTag().text()).toContain('Scheduled');
failure_reason: 'some reason',
},
}); });
expect(findFailureTag().text()).toContain('error'); it('should render the fork badge when the pipeline was run in a fork', () => {
expect(findFailureTag().attributes('title')).toContain('some reason'); const forkedPipeline = defaultProps.pipeline;
}); forkedPipeline.project.full_path = '/test/forked';
it('should render scheduled badge when pipeline was triggered by a schedule', () => { createComponent({
createComponent({ ...forkedPipeline,
pipeline: { });
flags: {},
source: 'schedule', expect(findForkTag().exists()).toBe(true);
}, expect(findForkTag().text()).toBe('fork');
}); });
expect(findScheduledTag().exists()).toBe(true); it('should render the train badge when the pipeline is a merge train pipeline', () => {
expect(findScheduledTag().text()).toContain('Scheduled'); const mergeTrainPipeline = defaultProps.pipeline;
}); mergeTrainPipeline.flags.merge_train_pipeline = true;
it('should render the fork badge when the pipeline was run in a fork', () => { createComponent({
createComponent({ ...mergeTrainPipeline,
pipeline: { });
flags: {},
project: { fullPath: '/test/forked' }, expect(findTrainTag().text()).toContain('train');
},
}); });
expect(findForkTag().exists()).toBe(true); it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
expect(findForkTag().text()).toBe('fork'); const mergeTrainPipeline = defaultProps.pipeline;
}); mergeTrainPipeline.flags.merge_train_pipeline = false;
it('should render the train badge when the pipeline is a merge train pipeline', () => { createComponent({
createComponent({ ...mergeTrainPipeline,
pipeline: { });
flags: {
merge_train_pipeline: true, expect(findTrainTag().exists()).toBe(false);
},
},
}); });
expect(findTrainTag().text()).toContain('train'); it('should not render the commit wrapper and commit-short-sha', () => {
}); createComponent();
it('should not render the train badge when the pipeline is not a merge train pipeline', () => { expect(findCommitTitleContainer().exists()).toBe(false);
createComponent({ expect(findCommitShortSha().exists()).toBe(false);
pipeline: {
flags: {
merge_train_pipeline: false,
},
},
}); });
});
describe('with the rearrangePipelinesTable feature flag turned on', () => {
it('should render the commit title, commit reference and commit-short-sha', () => {
createComponent({}, true);
const commitWrapper = findCommitTitleContainer();
expect(findTrainTag().exists()).toBe(false); expect(findCommitTitle(commitWrapper).exists()).toBe(true);
expect(findRefName().exists()).toBe(true);
expect(findCommitShortSha().exists()).toBe(true);
});
}); });
}); });
...@@ -33,13 +33,18 @@ describe('Pipelines Table', () => { ...@@ -33,13 +33,18 @@ describe('Pipelines Table', () => {
return pipelines.find((p) => p.user !== null && p.commit !== null); return pipelines.find((p) => p.user !== null && p.commit !== null);
}; };
const createComponent = (props = {}) => { const createComponent = (props = {}, rearrangePipelinesTable = false) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(PipelinesTable, { mount(PipelinesTable, {
propsData: { propsData: {
...defaultProps, ...defaultProps,
...props, ...props,
}, },
provide: {
glFeatures: {
rearrangePipelinesTable,
},
},
}), }),
); );
}; };
...@@ -71,7 +76,7 @@ describe('Pipelines Table', () => { ...@@ -71,7 +76,7 @@ describe('Pipelines Table', () => {
wrapper = null; wrapper = null;
}); });
describe('Pipelines Table', () => { describe('Pipelines Table with rearrangePipelinesTable feature flag turned off', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' }); createComponent({ pipelines: [pipeline], viewType: 'root' });
}); });
...@@ -189,4 +194,29 @@ describe('Pipelines Table', () => { ...@@ -189,4 +194,29 @@ describe('Pipelines Table', () => {
}); });
}); });
}); });
describe('Pipelines Table with rearrangePipelinesTable feature flag turned on', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' }, true);
});
it('should render table head with correct columns', () => {
expect(findStatusTh().text()).toBe('Status');
expect(findPipelineTh().text()).toBe('Pipeline');
expect(findStagesTh().text()).toBe('Stages');
expect(findActionsTh().text()).toBe('Actions');
});
describe('triggerer cell', () => {
it('should render the pipeline triggerer', () => {
expect(findTriggerer().exists()).toBe(true);
});
});
describe('commit cell', () => {
it('should not render commit information', () => {
expect(findCommit().exists()).toBe(false);
});
});
});
}); });
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