Commit 3247bf14 authored by Miguel Rincon's avatar Miguel Rincon Committed by Olena Horal-Koretska

Remove ci_mini_pipeline_gl_dropdown feature flag

This change removes the ci_mini_pipeline_gl_dropdown feature flag
and related code.
parent 43698b7b
......@@ -12,11 +12,9 @@
* 4. Commit widget
*/
import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import $ from 'jquery';
import { deprecatedCreateFlash as Flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { PIPELINES_TABLE } from '../../constants';
import eventHub from '../../event_hub';
import JobItem from '../graph/job_item.vue';
......@@ -31,19 +29,16 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
stage: {
type: Object,
required: true,
},
updateDropdown: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
......@@ -57,11 +52,6 @@ export default {
};
},
computed: {
isCiMiniPipelineGlDropdown() {
// Feature flag ci_mini_pipeline_gl_dropdown
// See more at https://gitlab.com/gitlab-org/gitlab/-/issues/300400
return this.glFeatures?.ciMiniPipelineGlDropdown;
},
triggerButtonClass() {
return `ci-status-icon-${this.stage.status.group}`;
},
......@@ -76,24 +66,12 @@ export default {
}
},
},
updated() {
if (!this.isCiMiniPipelineGlDropdown && this.dropdownContent.length) {
this.stopDropdownClickPropagation();
}
},
methods: {
onShowDropdown() {
eventHub.$emit('clickedDropdown');
this.isLoading = true;
this.fetchJobs();
},
onClickStage() {
if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
this.isLoading = true;
this.fetchJobs();
}
},
fetchJobs() {
axios
.get(this.stage.dropdown_path)
......@@ -102,133 +80,60 @@ export default {
this.isLoading = false;
})
.catch(() => {
if (this.isCiMiniPipelineGlDropdown) {
this.$refs.stageGlDropdown.hide();
} else {
this.closeDropdown();
}
this.$refs.stageGlDropdown.hide();
this.isLoading = false;
Flash(__('Something went wrong on our end.'));
});
},
/**
* When the user right clicks or cmd/ctrl + click in the job name
* the dropdown should not be closed and the link should open in another tab,
* so we stop propagation of the click event inside the dropdown.
*
* Since this component is rendered multiple times per page we need to guarantee we only
* target the click event of this component.
*
* Note: This should be removed once ci_mini_pipeline_gl_dropdown FF is removed as true.
*/
stopDropdownClickPropagation() {
$(
'.js-builds-dropdown-list button, .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item',
this.$el,
).on('click', (e) => {
e.stopPropagation();
});
},
closeDropdown() {
if (this.isDropdownOpen()) {
$(this.$refs.dropdown).dropdown('toggle');
}
},
isDropdownOpen() {
return this.$el.classList.contains('show');
},
pipelineActionRequestComplete() {
if (this.type === PIPELINES_TABLE) {
// warn the table to update
// warn the pipelines table to update
eventHub.$emit('refreshPipelinesTable');
return;
}
// close the dropdown in mr widget
if (this.isCiMiniPipelineGlDropdown) {
this.$refs.stageGlDropdown.hide();
} else {
$(this.$refs.dropdown).dropdown('toggle');
}
// close the dropdown in MR widget
this.$refs.stageGlDropdown.hide();
},
},
};
</script>
<template>
<div class="dropdown">
<gl-dropdown
v-if="isCiMiniPipelineGlDropdown"
ref="stageGlDropdown"
v-gl-tooltip.hover
data-testid="mini-pipeline-graph-dropdown"
:title="stage.title"
variant="link"
:lazy="true"
:popper-opts="{ placement: 'bottom' }"
:toggle-class="['mini-pipeline-graph-gl-dropdown-toggle', triggerButtonClass]"
menu-class="mini-pipeline-graph-dropdown-menu"
@show="onShowDropdown"
>
<template #button-content>
<span class="gl-pointer-events-none">
<gl-icon :name="borderlessIcon" />
</span>
</template>
<gl-loading-icon v-if="isLoading" />
<ul
v-else
class="js-builds-dropdown-list scrollable-menu"
data-testid="mini-pipeline-graph-dropdown-menu-list"
>
<li v-for="job in dropdownContent" :key="job.id">
<job-item
:dropdown-length="dropdownContent.length"
:job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</li>
</ul>
</gl-dropdown>
<template v-else>
<button
id="stageDropdown"
ref="dropdown"
v-gl-tooltip.hover
:class="triggerButtonClass"
:title="stage.title"
class="mini-pipeline-graph-dropdown-toggle"
data-testid="mini-pipeline-graph-dropdown-toggle"
data-toggle="dropdown"
data-display="static"
type="button"
aria-haspopup="true"
aria-expanded="false"
@click="onClickStage"
>
<span :aria-label="stage.title" aria-hidden="true" class="gl-pointer-events-none">
<gl-icon :name="borderlessIcon" />
</span>
</button>
<div
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
aria-labelledby="stageDropdown"
>
<gl-loading-icon v-if="isLoading" />
<ul v-else class="js-builds-dropdown-list scrollable-menu">
<li v-for="job in dropdownContent" :key="job.id">
<job-item
:dropdown-length="dropdownContent.length"
:job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</li>
</ul>
</div>
<gl-dropdown
ref="stageGlDropdown"
v-gl-tooltip.hover
data-testid="mini-pipeline-graph-dropdown"
:title="stage.title"
variant="link"
:lazy="true"
:popper-opts="{ placement: 'bottom' }"
:toggle-class="['mini-pipeline-graph-dropdown-toggle', triggerButtonClass]"
menu-class="mini-pipeline-graph-dropdown-menu"
@show="onShowDropdown"
>
<template #button-content>
<span class="gl-pointer-events-none">
<gl-icon :name="borderlessIcon" />
</span>
</template>
</div>
<gl-loading-icon v-if="isLoading" />
<ul
v-else
class="js-builds-dropdown-list scrollable-menu"
data-testid="mini-pipeline-graph-dropdown-menu-list"
>
<li v-for="job in dropdownContent" :key="job.id">
<job-item
:dropdown-length="dropdownContent.length"
:job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
</li>
</ul>
</gl-dropdown>
</template>
......@@ -67,8 +67,7 @@
// Mini Pipelines
.stage-cell {
.mini-pipeline-graph-dropdown-toggle,
.mini-pipeline-graph-gl-dropdown-toggle {
.mini-pipeline-graph-dropdown-toggle {
svg {
height: $ci-action-icon-size;
width: $ci-action-icon-size;
......@@ -138,14 +137,16 @@
}
}
// Dropdown button in mini pipeline graph
// Commit mini pipeline (HAML)
button.mini-pipeline-graph-dropdown-toggle,
// As the `mini-pipeline-item` mixin specificity is lower
// than the toggle of dropdown with 'variant="link"' we add
// classes ".gl-button.btn-link" to make it more specific.
// Once FF ci_mini_pipeline_gl_dropdown is removed, the `mini-pipeline-item`
// itself could increase its specificity to simplify this selector
button.gl-button.btn-link.mini-pipeline-graph-gl-dropdown-toggle {
// GlDropdown mini pipeline (Vue)
// As the `mini-pipeline-item` mixin specificity is lower
// than the toggle of dropdown with 'variant="link"' we add
// classes ".gl-button.btn-link" to make it more specific
// and avoid having the size overriden
//
// See https://gitlab.com/gitlab-org/gitlab/-/issues/320737
button.gl-button.btn-link.mini-pipeline-graph-dropdown-toggle {
@include mini-pipeline-item();
}
......
......@@ -226,10 +226,6 @@ $tabs-holder-z-index: 250;
}
}
.mini-pipeline-graph-dropdown-toggle {
vertical-align: top;
}
.normal {
flex: 1;
flex-basis: auto;
......@@ -982,15 +978,15 @@ $tabs-holder-z-index: 250;
line-height: initial;
}
.mini-pipeline-graph-dropdown-toggle,
.stage-cell .mini-pipeline-graph-dropdown-toggle svg,
// As the `mini-pipeline-item` mixin specificity is lower
// than the toggle of dropdown with 'variant="link"' we add
// classes ".gl-button.btn-link" to make it more specific.
// Once FF ci_mini_pipeline_gl_dropdown is removed, the `mini-pipeline-item`
// itself could increase its specificity to simplify this selector
button.gl-button.btn-link.mini-pipeline-graph-gl-dropdown-toggle,
.stage-cell button.gl-button.btn-link.mini-pipeline-graph-gl-dropdown-toggle svg {
// GlDropdown mini pipeline (Vue)
// As the `mini-pipeline-item` mixin specificity is lower
// than the toggle of dropdown with 'variant="link"' we add
// classes ".gl-button.btn-link" to make it more specific
// and avoid having the size overriden
//
// See https://gitlab.com/gitlab-org/gitlab/-/issues/320737
button.gl-button.btn-link.mini-pipeline-graph-dropdown-toggle,
.stage-cell button.gl-button.btn-link.mini-pipeline-graph-dropdown-toggle svg {
height: $ci-action-icon-size-lg;
width: $ci-action-icon-size-lg;
}
......
......@@ -18,9 +18,6 @@ class Projects::CommitController < Projects::ApplicationController
before_action :define_commit_vars, only: [:show, :diff_for_path, :diff_files, :pipelines, :merge_requests]
before_action :define_note_vars, only: [:show, :diff_for_path, :diff_files]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
before_action only: [:pipelines] do
push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, @project, type: :development, default_enabled: :yaml)
end
BRANCH_SEARCH_LIMIT = 1000
COMMIT_DIFFS_PER_PAGE = 75
......
......@@ -44,7 +44,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:suggestions_custom_commit, @project, default_enabled: true)
push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, @project, type: :development, default_enabled: :yaml)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)
......
......@@ -17,7 +17,6 @@ class Projects::PipelinesController < Projects::ApplicationController
push_frontend_feature_flag(:new_pipeline_form, project, 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(:ci_mini_pipeline_gl_dropdown, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:jira_for_vulnerabilities, project, type: :development, default_enabled: :yaml)
end
before_action :ensure_pipeline, only: [:show]
......
---
name: ci_mini_pipeline_gl_dropdown
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52821
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300400
milestone: '13.9'
type: development
group: group::continuous integration
default_enabled: true
......@@ -18,7 +18,6 @@ RSpec.describe 'User adds a merge request to a merge train', :js do
before do
stub_feature_flags(disable_merge_trains: false)
stub_feature_flags(ci_mini_pipeline_gl_dropdown: false)
stub_licensed_features(merge_pipelines: true, merge_trains: true)
project.add_maintainer(user)
project.update!(merge_pipelines_enabled: true, merge_trains_enabled: true)
......@@ -70,11 +69,11 @@ RSpec.describe 'User adds a merge request to a merge train', :js do
end
it 'displays pipeline control' do
expect(page).to have_selector('[data-testid="mini-pipeline-graph-dropdown-toggle"]')
expect(page).to have_selector('[data-testid="mini-pipeline-graph-dropdown"]')
end
it 'does not allow retry for merge train pipeline' do
find('[data-testid="mini-pipeline-graph-dropdown-toggle"]').click
find('[data-testid="mini-pipeline-graph-dropdown"] .dropdown-toggle').click
page.within '.ci-job-component' do
expect(page).to have_selector('.ci-status-icon')
expect(page).not_to have_selector('.retry')
......
......@@ -9,166 +9,149 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js do
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running', sha: project.commit.id) }
let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test') }
shared_examples 'mini pipeline renders' do |ci_mini_pipeline_gl_dropdown_enabled|
before do
build.run
build.trace.set('hello')
sign_in(user)
stub_feature_flags(ci_mini_pipeline_gl_dropdown: ci_mini_pipeline_gl_dropdown_enabled)
visit_merge_request
end
dropdown_selector = '[data-testid="mini-pipeline-graph-dropdown"]'
let_it_be(:dropdown_toggle_selector) do
if ci_mini_pipeline_gl_dropdown_enabled
'[data-testid="mini-pipeline-graph-dropdown"] .dropdown-toggle'
else
'[data-testid="mini-pipeline-graph-dropdown-toggle"]'
end
end
before do
build.run
build.trace.set('hello')
sign_in(user)
visit_merge_request
end
def visit_merge_request(format: :html, serializer: nil)
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
def visit_merge_request(format: :html, serializer: nil)
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
it 'displays a mini pipeline graph' do
expect(page).to have_selector('.mr-widget-pipeline-graph')
end
it 'displays a mini pipeline graph' do
expect(page).to have_selector('.mr-widget-pipeline-graph')
end
context 'as json' do
let(:artifacts_file1) { fixture_file_upload(File.join('spec/fixtures/banana_sample.gif'), 'image/gif') }
let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') }
context 'as json' do
let(:artifacts_file1) { fixture_file_upload(File.join('spec/fixtures/banana_sample.gif'), 'image/gif') }
let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') }
before do
job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
create(:ci_job_artifact, :archive, file: artifacts_file1, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
end
before do
job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
create(:ci_job_artifact, :archive, file: artifacts_file1, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
end
# TODO: https://gitlab.com/gitlab-org/gitlab-foss/issues/48034
xit 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
# TODO: https://gitlab.com/gitlab-org/gitlab-foss/issues/48034
xit 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
create(:ci_job_artifact, :archive, file: artifacts_file2, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
create(:ci_job_artifact, :archive, file: artifacts_file2, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
expect(before.count).to eq(after.count)
expect(before.cached_count).to eq(after.cached_count)
end
expect(before.count).to eq(after.count)
expect(before.cached_count).to eq(after.cached_count)
end
end
describe 'build list toggle' do
let(:toggle) do
find(dropdown_toggle_selector)
first(dropdown_toggle_selector)
end
describe 'build list toggle' do
let(:toggle) do
find(dropdown_selector)
first(dropdown_selector)
end
# Status icon button styles should update as described in
# https://gitlab.com/gitlab-org/gitlab-foss/issues/42769
it 'has unique styles for default, :hover, :active, and :focus states' do
default_background_color, default_foreground_color, default_box_shadow = get_toggle_colors(dropdown_toggle_selector)
# Status icon button styles should update as described in
# https://gitlab.com/gitlab-org/gitlab-foss/issues/42769
it 'has unique styles for default, :hover, :active, and :focus states' do
default_background_color, default_foreground_color, default_box_shadow = get_toggle_colors(dropdown_selector)
toggle.hover
hover_background_color, hover_foreground_color, hover_box_shadow = get_toggle_colors(dropdown_toggle_selector)
toggle.hover
hover_background_color, hover_foreground_color, hover_box_shadow = get_toggle_colors(dropdown_selector)
page.driver.browser.action.click_and_hold(toggle.native).perform
active_background_color, active_foreground_color, active_box_shadow = get_toggle_colors(dropdown_toggle_selector)
page.driver.browser.action.release(toggle.native).perform
page.driver.browser.action.click_and_hold(toggle.native).perform
active_background_color, active_foreground_color, active_box_shadow = get_toggle_colors(dropdown_selector)
page.driver.browser.action.release(toggle.native).perform
page.driver.browser.action.click(toggle.native).move_by(100, 100).perform
focus_background_color, focus_foreground_color, focus_box_shadow = get_toggle_colors(dropdown_toggle_selector)
page.driver.browser.action.click(toggle.native).move_by(100, 100).perform
focus_background_color, focus_foreground_color, focus_box_shadow = get_toggle_colors(dropdown_selector)
expect(default_background_color).not_to eq(hover_background_color)
expect(hover_background_color).not_to eq(active_background_color)
expect(default_background_color).not_to eq(active_background_color)
expect(default_background_color).not_to eq(hover_background_color)
expect(hover_background_color).not_to eq(active_background_color)
expect(default_background_color).not_to eq(active_background_color)
expect(default_foreground_color).not_to eq(hover_foreground_color)
expect(hover_foreground_color).not_to eq(active_foreground_color)
expect(default_foreground_color).not_to eq(active_foreground_color)
expect(default_foreground_color).not_to eq(hover_foreground_color)
expect(hover_foreground_color).not_to eq(active_foreground_color)
expect(default_foreground_color).not_to eq(active_foreground_color)
expect(focus_background_color).to eq(hover_background_color)
expect(focus_foreground_color).to eq(hover_foreground_color)
expect(focus_background_color).to eq(hover_background_color)
expect(focus_foreground_color).to eq(hover_foreground_color)
expect(default_box_shadow).to eq('none')
expect(hover_box_shadow).to eq('none')
expect(active_box_shadow).not_to eq('none')
expect(focus_box_shadow).not_to eq('none')
end
expect(default_box_shadow).to eq('none')
expect(hover_box_shadow).to eq('none')
expect(active_box_shadow).not_to eq('none')
expect(focus_box_shadow).not_to eq('none')
end
it 'shows tooltip when hovered' do
toggle.hover
it 'shows tooltip when hovered' do
toggle.hover
expect(page).to have_selector('.tooltip')
end
expect(page).to have_selector('.tooltip')
end
end
describe 'builds list menu' do
let(:toggle) do
find(dropdown_toggle_selector)
first(dropdown_toggle_selector)
end
describe 'builds list menu' do
let(:toggle) do
find(dropdown_selector)
first(dropdown_selector)
end
before do
toggle.click
wait_for_requests
end
before do
toggle.click
wait_for_requests
end
it 'pens when toggle is clicked' do
expect(toggle.find(:xpath, '..')).to have_selector('.mini-pipeline-graph-dropdown-menu')
end
it 'pens when toggle is clicked' do
expect(toggle.find(:xpath, '..')).to have_selector('.mini-pipeline-graph-dropdown-menu')
end
it 'closes when toggle is clicked again' do
toggle.click
it 'closes when toggle is clicked again' do
toggle.click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
it 'closes when clicking somewhere else' do
find('body').click
it 'closes when clicking somewhere else' do
find('body').click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
describe 'build list build item' do
let(:build_item) do
find('.mini-pipeline-graph-dropdown-item')
first('.mini-pipeline-graph-dropdown-item')
end
describe 'build list build item' do
let(:build_item) do
find('.mini-pipeline-graph-dropdown-item')
first('.mini-pipeline-graph-dropdown-item')
end
it 'visits the build page when clicked' do
build_item.click
find('.build-page')
it 'visits the build page when clicked' do
build_item.click
find('.build-page')
expect(current_path).to eql(project_job_path(project, build))
end
expect(current_path).to eql(project_job_path(project, build))
end
it 'shows tooltip when hovered' do
build_item.hover
it 'shows tooltip when hovered' do
build_item.hover
expect(page).to have_selector('.tooltip')
end
expect(page).to have_selector('.tooltip')
end
end
end
context 'with ci_mini_pipeline_gl_dropdown disabled' do
it_behaves_like "mini pipeline renders", false
end
context 'with ci_mini_pipeline_gl_dropdown enabled' do
it_behaves_like "mini pipeline renders", true
end
private
def get_toggle_colors(selector)
find(selector)
[
evaluate_script("$('#{selector}:visible').css('background-color');"),
evaluate_script("$('#{selector}:visible svg').css('fill');"),
evaluate_script("$('#{selector}:visible').css('box-shadow');")
evaluate_script("$('#{selector} button:visible').css('background-color');"),
evaluate_script("$('#{selector} button:visible svg').css('fill');"),
evaluate_script("$('#{selector} button:visible').css('box-shadow');")
]
end
end
......@@ -519,75 +519,58 @@ RSpec.describe 'Pipelines', :js do
end
end
shared_examples 'mini pipeline renders' do |ci_mini_pipeline_gl_dropdown_enabled|
context 'mini pipeline graph' do
let!(:build) do
create(:ci_build, :pending, pipeline: pipeline,
stage: 'build',
name: 'build')
end
context 'mini pipeline graph' do
let!(:build) do
create(:ci_build, :pending, pipeline: pipeline,
stage: 'build',
name: 'build')
end
before do
stub_feature_flags(ci_mini_pipeline_gl_dropdown: ci_mini_pipeline_gl_dropdown_enabled)
visit_project_pipelines
end
dropdown_selector = '[data-testid="mini-pipeline-graph-dropdown"]'
let_it_be(:dropdown_toggle_selector) do
if ci_mini_pipeline_gl_dropdown_enabled
'[data-testid="mini-pipeline-graph-dropdown"] .dropdown-toggle'
else
'[data-testid="mini-pipeline-graph-dropdown-toggle"]'
end
end
before do
visit_project_pipelines
end
it 'renders a mini pipeline graph' do
expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]')
expect(page).to have_selector(dropdown_toggle_selector)
end
it 'renders a mini pipeline graph' do
expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]')
expect(page).to have_selector(dropdown_selector)
end
context 'when clicking a stage badge' do
it 'opens a dropdown' do
find(dropdown_toggle_selector).click
context 'when clicking a stage badge' do
it 'opens a dropdown' do
find(dropdown_selector).click
expect(page).to have_link build.name
end
expect(page).to have_link build.name
end
it 'is possible to cancel pending build' do
find(dropdown_toggle_selector).click
find('.js-ci-action').click
wait_for_requests
it 'is possible to cancel pending build' do
find(dropdown_selector).click
find('.js-ci-action').click
wait_for_requests
expect(build.reload).to be_canceled
end
expect(build.reload).to be_canceled
end
end
context 'for a failed pipeline' do
let!(:build) do
create(:ci_build, :failed, pipeline: pipeline,
stage: 'build',
name: 'build')
end
context 'for a failed pipeline' do
let!(:build) do
create(:ci_build, :failed, pipeline: pipeline,
stage: 'build',
name: 'build')
end
it 'displays the failure reason' do
find(dropdown_toggle_selector).click
it 'displays the failure reason' do
find(dropdown_selector).click
within('.js-builds-dropdown-list') do
build_element = page.find('.mini-pipeline-graph-dropdown-item')
expect(build_element['title']).to eq('build - failed - (unknown failure)')
end
within('.js-builds-dropdown-list') do
build_element = page.find('.mini-pipeline-graph-dropdown-item')
expect(build_element['title']).to eq('build - failed - (unknown failure)')
end
end
end
end
context 'with ci_mini_pipeline_gl_dropdown disabled' do
it_behaves_like "mini pipeline renders", false
end
context 'with ci_mini_pipeline_gl_dropdown enabled' do
it_behaves_like "mini pipeline renders", true
end
context 'with pagination' do
before do
allow(Ci::Pipeline).to receive(:default_per_page).and_return(1)
......
......@@ -69,7 +69,8 @@ describe('Pipelines', () => {
const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button');
const findStagesDropdown = () => wrapper.findByTestId('mini-pipeline-graph-dropdown-toggle');
const findStagesDropdownToggle = () =>
wrapper.find('[data-testid="mini-pipeline-graph-dropdown"] .dropdown-toggle');
const findPipelineUrlLinks = () => wrapper.findAll('[data-testid="pipeline-url-link"]');
const createComponent = (props = defaultProps) => {
......@@ -642,7 +643,7 @@ describe('Pipelines', () => {
// Mock init a polling cycle
wrapper.vm.poll.options.notificationCallback(true);
findStagesDropdown().trigger('click');
findStagesDropdownToggle().trigger('click');
await waitForPromises();
......@@ -652,7 +653,9 @@ describe('Pipelines', () => {
});
it('stops polling & restarts polling', async () => {
findStagesDropdown().trigger('click');
findStagesDropdownToggle().trigger('click');
await waitForPromises();
expect(cancelMock).not.toHaveBeenCalled();
expect(stopMock).toHaveBeenCalled();
......
......@@ -153,11 +153,10 @@ describe('Pipelines Table Row', () => {
});
it('should render an icon for each stage', () => {
expect(
wrapper.findAll(
'.table-section:nth-child(5) [data-testid="mini-pipeline-graph-dropdown-toggle"]',
).length,
).toEqual(pipeline.details.stages.length);
const stages = wrapper.findAll(
'.table-section:nth-child(5) [data-testid="mini-pipeline-graph-dropdown"]',
);
expect(stages).toHaveLength(pipeline.details.stages.length);
});
});
......
import 'bootstrap/js/dist/dropdown';
import { GlDropdown } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import StageComponent from '~/pipelines/components/pipelines_list/stage.vue';
import eventHub from '~/pipelines/event_hub';
import { stageReply } from './mock_data';
const dropdownPath = 'path.json';
describe('Pipelines stage component', () => {
let wrapper;
let mock;
let glFeatures;
const defaultProps = {
stage: {
status: {
group: 'success',
icon: 'status_success',
title: 'success',
},
dropdown_path: 'path.json',
},
updateDropdown: false,
};
const createComponent = (props = {}) => {
wrapper = mount(StageComponent, {
attachTo: document.body,
propsData: {
...defaultProps,
stage: {
status: {
group: 'success',
icon: 'status_success',
title: 'success',
},
dropdown_path: dropdownPath,
},
updateDropdown: false,
...props,
},
provide: {
glFeatures,
},
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
jest.spyOn(eventHub, '$emit');
glFeatures = {};
});
afterEach(() => {
......@@ -52,245 +43,142 @@ describe('Pipelines stage component', () => {
mock.restore();
});
describe('when ci_mini_pipeline_gl_dropdown feature flag is disabled', () => {
const isDropdownOpen = () => wrapper.classes('show');
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownToggle = () => wrapper.find('button.dropdown-toggle');
const findDropdownMenu = () =>
wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]');
const findCiActionBtn = () => wrapper.find('.js-ci-action');
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('should render a dropdown with the status icon', () => {
expect(wrapper.attributes('class')).toEqual('dropdown');
expect(wrapper.find('svg').exists()).toBe(true);
expect(wrapper.find('button').attributes('data-toggle')).toEqual('dropdown');
});
const openStageDropdown = () => {
findDropdownToggle().trigger('click');
return new Promise((resolve) => {
wrapper.vm.$root.$on('bv::dropdown::show', resolve);
});
};
describe('with successful request', () => {
beforeEach(() => {
mock.onGet('path.json').reply(200, stageReply);
createComponent();
});
it('should render the received data and emit `clickedDropdown` event', async () => {
wrapper.find('button').trigger('click');
await axios.waitForAll();
expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
stageReply.latest_statuses[0].name,
);
describe('default appearance', () => {
beforeEach(() => {
createComponent();
});
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
});
it('should render a dropdown with the status icon', () => {
expect(findDropdown().exists()).toBe(true);
expect(findDropdownToggle().exists()).toBe(true);
expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true);
});
});
it('when request fails should close the dropdown', async () => {
mock.onGet('path.json').reply(500);
describe('when update dropdown is changed', () => {
beforeEach(() => {
createComponent();
wrapper.find({ ref: 'dropdown' }).trigger('click');
});
});
expect(isDropdownOpen()).toBe(true);
describe('when user opens dropdown and stage request is successful', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(200, stageReply);
createComponent();
wrapper.find('button').trigger('click');
await openStageDropdown();
await axios.waitForAll();
expect(isDropdownOpen()).toBe(false);
});
describe('update endpoint correctly', () => {
beforeEach(() => {
const copyStage = { ...stageReply };
copyStage.latest_statuses[0].name = 'this is the updated content';
mock.onGet('bar.json').reply(200, copyStage);
createComponent({
stage: {
status: {
group: 'running',
icon: 'status_running',
title: 'running',
},
dropdown_path: 'bar.json',
},
});
return axios.waitForAll();
});
it('should update the stage to request the new endpoint provided', async () => {
wrapper.find('button').trigger('click');
await axios.waitForAll();
expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
'this is the updated content',
);
});
it('should render the received data and emit `clickedDropdown` event', async () => {
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
});
describe('pipelineActionRequestComplete', () => {
beforeEach(() => {
mock.onGet('path.json').reply(200, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
});
const clickCiAction = async () => {
wrapper.find('button').trigger('click');
await axios.waitForAll();
wrapper.find('.js-ci-action').trigger('click');
await axios.waitForAll();
};
describe('within pipeline table', () => {
it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
createComponent({ type: 'PIPELINES_TABLE' });
await clickCiAction();
expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
});
});
describe('in MR widget', () => {
beforeEach(() => {
jest.spyOn($.fn, 'dropdown');
});
it('should refresh when updateDropdown is set to true', async () => {
expect(mock.history.get).toHaveLength(1);
it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
createComponent();
await clickCiAction();
wrapper.setProps({ updateDropdown: true });
await axios.waitForAll();
expect($.fn.dropdown).toHaveBeenCalledWith('toggle');
});
});
expect(mock.history.get).toHaveLength(2);
});
});
describe('when ci_mini_pipeline_gl_dropdown feature flag is enabled', () => {
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownToggle = () => wrapper.find('button.gl-dropdown-toggle');
const findDropdownMenu = () =>
wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]');
const findCiActionBtn = () => wrapper.find('.js-ci-action');
const openGlDropdown = () => {
findDropdownToggle().trigger('click');
return new Promise((resolve) => {
wrapper.vm.$root.$on('bv::dropdown::show', resolve);
});
};
describe('when user opens dropdown and stage request fails', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(500);
createComponent();
beforeEach(() => {
glFeatures = { ciMiniPipelineGlDropdown: true };
await openStageDropdown();
await axios.waitForAll();
});
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('should render a dropdown with the status icon', () => {
expect(findDropdown().exists()).toBe(true);
expect(findDropdownToggle().classes('gl-dropdown-toggle')).toEqual(true);
expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true);
});
it('should close the dropdown', () => {
expect(findDropdown().classes('show')).toBe(false);
});
});
describe('with successful request', () => {
beforeEach(() => {
mock.onGet('path.json').reply(200, stageReply);
createComponent();
describe('update endpoint correctly', () => {
beforeEach(async () => {
const copyStage = { ...stageReply };
copyStage.latest_statuses[0].name = 'this is the updated content';
mock.onGet('bar.json').reply(200, copyStage);
createComponent({
stage: {
status: {
group: 'running',
icon: 'status_running',
title: 'running',
},
dropdown_path: 'bar.json',
},
});
await axios.waitForAll();
});
it('should render the received data and emit `clickedDropdown` event', async () => {
await openGlDropdown();
await axios.waitForAll();
it('should update the stage to request the new endpoint provided', async () => {
await openStageDropdown();
await axios.waitForAll();
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
});
expect(findDropdownMenu().text()).toContain('this is the updated content');
});
});
it('when request fails should close the dropdown', async () => {
mock.onGet('path.json').reply(500);
createComponent();
describe('pipelineActionRequestComplete', () => {
beforeEach(() => {
mock.onGet(dropdownPath).reply(200, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
});
await openGlDropdown();
const clickCiAction = async () => {
await openStageDropdown();
await axios.waitForAll();
expect(findDropdown().classes('show')).toBe(false);
});
findCiActionBtn().trigger('click');
await axios.waitForAll();
};
describe('update endpoint correctly', () => {
beforeEach(async () => {
const copyStage = { ...stageReply };
copyStage.latest_statuses[0].name = 'this is the updated content';
mock.onGet('bar.json').reply(200, copyStage);
createComponent({
stage: {
status: {
group: 'running',
icon: 'status_running',
title: 'running',
},
dropdown_path: 'bar.json',
},
});
await axios.waitForAll();
describe('within pipeline table', () => {
beforeEach(() => {
createComponent({ type: 'PIPELINES_TABLE' });
});
it('should update the stage to request the new endpoint provided', async () => {
await openGlDropdown();
await axios.waitForAll();
it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
await clickCiAction();
expect(findDropdownMenu().text()).toContain('this is the updated content');
expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
});
});
describe('pipelineActionRequestComplete', () => {
describe('in MR widget', () => {
beforeEach(() => {
mock.onGet('path.json').reply(200, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
});
const clickCiAction = async () => {
await openGlDropdown();
await axios.waitForAll();
findCiActionBtn().trigger('click');
await axios.waitForAll();
};
describe('within pipeline table', () => {
beforeEach(() => {
createComponent({ type: 'PIPELINES_TABLE' });
});
it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
await clickCiAction();
expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
});
createComponent();
});
describe('in MR widget', () => {
beforeEach(() => {
jest.spyOn($.fn, 'dropdown');
createComponent();
});
it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
const hidden = jest.fn();
it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
const hidden = jest.fn();
wrapper.vm.$root.$on('bv::dropdown::hide', hidden);
wrapper.vm.$root.$on('bv::dropdown::hide', hidden);
expect(hidden).toHaveBeenCalledTimes(0);
expect(hidden).toHaveBeenCalledTimes(0);
await clickCiAction();
await clickCiAction();
expect(hidden).toHaveBeenCalledTimes(1);
});
expect(hidden).toHaveBeenCalledTimes(1);
});
});
});
......
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