Commit 2516aeb2 authored by Mark Florian's avatar Mark Florian

Merge branch '300403-replace-commit-box-minipipeline-remove-ff' into 'master'

Remove ci_commit_pipeline_mini_graph_vue flag [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!56510
parents a8310e4d e1fd7d0d
import $ from 'jquery';
import { deprecatedCreateFlash as flash } from './flash';
import axios from './lib/utils/axios_utils';
import { __ } from './locale';
/**
* In each pipelines table we have a mini pipeline graph for each pipeline.
*
* When we click in a pipeline stage, we need to make an API call to get the
* builds list to render in a dropdown.
*
* The container should be the table element.
*
* The stage icon clicked needs to have the following HTML structure:
* <div class="dropdown">
* <button class="dropdown js-builds-dropdown-button" data-toggle="dropdown"></button>
* <div class="js-builds-dropdown-container dropdown-menu"></div>
* </div>
*/
export default class MiniPipelineGraph {
constructor(opts = {}) {
this.container = opts.container || '';
this.dropdownListSelector = '.js-builds-dropdown-container';
this.getBuildsList = this.getBuildsList.bind(this);
}
/**
* Adds the event listener when the dropdown is opened.
* All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
$(document)
.off('shown.bs.dropdown', this.container)
.on('shown.bs.dropdown', this.container, this.getBuildsList);
}
/**
* 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.
*/
stopDropdownClickPropagation() {
$(document).on(
'click',
`${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`,
(e) => {
e.stopPropagation();
},
);
}
/**
* For the clicked stage, renders the given data in the dropdown list.
*
* @param {HTMLElement} stageContainer
* @param {Object} data
*/
renderBuildsList(stageContainer, data) {
const dropdownContainer = stageContainer.parentElement.querySelector(
`${this.dropdownListSelector} .js-builds-dropdown-list ul`,
);
dropdownContainer.innerHTML = data;
}
/**
* For the clicked stage, gets the list of builds.
*
* All dropdown events have a relatedTarget property,
* whose value is the toggling anchor element.
*
* @param {Object} e bootstrap dropdown event
* @return {Promise}
*/
getBuildsList(e) {
const button = e.relatedTarget;
const endpoint = button.dataset.stageEndpoint;
this.renderBuildsList(button, '');
this.toggleLoading(button);
axios
.get(endpoint)
.then(({ data }) => {
this.toggleLoading(button);
this.renderBuildsList(button, data.html);
this.stopDropdownClickPropagation();
})
.catch(() => {
this.toggleLoading(button);
if ($(button).parent().hasClass('open')) {
$(button).dropdown('toggle');
}
flash(__('An error occurred while fetching the builds.'), 'alert');
});
}
/**
* Toggles the visibility of the loading icon.
*
* @param {HTMLElement} stageContainer
* @return {type}
*/
toggleLoading(stageContainer) {
stageContainer.parentElement
.querySelector(`${this.dropdownListSelector} .js-builds-dropdown-loading`)
.classList.toggle('hidden');
}
}
......@@ -36,7 +36,7 @@ export default {
};
</script>
<template>
<div data-testid="widget-mini-pipeline-graph">
<div data-testid="pipeline-mini-graph">
<div
v-for="stage in stages"
:key="stage.name"
......
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph';
import { initDetailsButton } from './init_details_button';
import { loadBranches } from './load_branches';
export const initCommitBoxInfo = (containerSelector = '.js-commit-box-info') => {
const containerEl = document.querySelector(containerSelector);
export const initCommitBoxInfo = () => {
// Display commit related branches
loadBranches(containerEl);
loadBranches();
// Related merge requests to this commit
fetchCommitMergeRequests();
// Display pipeline mini graph for this commit
// Feature flag ci_commit_pipeline_mini_graph_vue
if (gon.features.ciCommitPipelineMiniGraphVue) {
initCommitPipelineMiniGraph();
} else {
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
}
initDetailsButton();
};
......@@ -2,7 +2,8 @@ import axios from 'axios';
import { sanitize } from '~/lib/dompurify';
import { __ } from '~/locale';
export const loadBranches = (containerEl) => {
export const loadBranches = (containerSelector = '.js-commit-box-info') => {
const containerEl = document.querySelector(containerSelector);
if (!containerEl) {
return;
}
......
......@@ -19,11 +19,6 @@ class Projects::CommitController < Projects::ApplicationController
before_action :define_commit_box_vars, only: [:show, :pipelines]
before_action :define_note_vars, only: [:show, :diff_for_path, :diff_files]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
before_action only: [:show, :pipelines] do
push_frontend_feature_flag(:ci_commit_pipeline_mini_graph_vue, @project, default_enabled: :yaml)
end
before_action do
push_frontend_feature_flag(:pick_into_project)
end
......@@ -214,7 +209,6 @@ class Projects::CommitController < Projects::ApplicationController
def define_commit_box_vars
@last_pipeline = @commit.last_pipeline
return unless ::Gitlab::Ci::Features.ci_commit_pipeline_mini_graph_vue_enabled?(@project)
return unless @commit.last_pipeline
@last_pipeline_stages = StageSerializer.new(project: @project, current_user: @current_user).represent(@last_pipeline.stages)
......
......@@ -152,15 +152,6 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@stage, details: true, retried: params[:retried])
end
# TODO: This endpoint is used by mini-pipeline-graph
# TODO: This endpoint should be migrated to `stage.json`
def stage_ajax
@stage = pipeline.legacy_stage(params[:stage])
return not_found unless @stage
render json: { html: view_to_html_string('projects/pipelines/_stage') }
end
def retry
pipeline.retry_failed(current_user)
......
-# Renders the content of each li in the dropdown
- subject = local_assigns.fetch(:subject)
- status = subject.detailed_status(current_user)
- klass = "ci-status-icon ci-status-icon-#{status.group}"
- tooltip = "#{subject.name} - #{status.status_tooltip}"
- if status.has_details?
= link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item d-flex', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
%span{ class: klass }= sprite_icon(status.icon)
%span.gl-text-truncate.mw-70p.gl-pl-2= subject.name
- else
.menu-item.mini-pipeline-graph-dropdown-item.d-flex{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
%span{ class: klass }= sprite_icon(status.icon)
%span.gl-text-truncate.mw-70p.gl-pl-2= subject.name
- if status.has_action?
= link_to status.action_path, class: "gl-button ci-action-icon-container ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
= sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}")
......@@ -59,11 +59,8 @@
- if @last_pipeline.stages_count.nonzero?
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), @last_pipeline.stages_count) }
.mr-widget-pipeline-graph
- if ::Gitlab::Ci::Features.ci_commit_pipeline_mini_graph_vue_enabled?(@project)
.stage-cell
.js-commit-pipeline-mini-graph{ data: { stages: @last_pipeline_stages.to_json.html_safe } }
- else
= render 'shared/mini_pipeline_graph', pipeline: @last_pipeline, klass: 'js-commit-pipeline-graph'
- if @last_pipeline.duration
in
= time_interval_in_words @last_pipeline.duration
......
- grouped_statuses = @stage.statuses.latest_ordered.group_by(&:status)
- Ci::HasStatus::ORDERED_STATUSES.each do |ordered_status|
- grouped_statuses.fetch(ordered_status, []).each do |status|
%li
= render 'ci/status/dropdown_graph_badge', subject: status
.stage-cell
- pipeline.legacy_stages.each do |stage|
- if stage.status
- detailed_status = stage.detailed_status(current_user)
- icon_status = "#{detailed_status.icon}_borderless"
.stage-container.mt-0.ml-1.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_ajax_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
= sprite_icon(icon_status)
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
%li.js-builds-dropdown-list.scrollable-menu
%ul
%li.js-builds-dropdown-loading.hidden
.loading-container.text-center
%span.spinner{ 'aria-label': _('Loading') }
---
title: Update mini pipeline appearance in commit page to match other mini pipelines
in the application
merge_request: 56510
author:
type: changed
---
name: ci_commit_pipeline_mini_graph_vue
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55363
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323356
milestone: '13.10'
type: development
group: group::pipeline authoring
default_enabled: true
......@@ -12,7 +12,6 @@ resources :pipelines, only: [:index, :new, :create, :show, :destroy] do
member do
get :stage
get :stage_ajax
post :cancel
post :retry
get :builds
......
......@@ -64,10 +64,6 @@ module Gitlab
::Feature.enabled?(:multiple_cache_per_job, default_enabled: :yaml)
end
def self.ci_commit_pipeline_mini_graph_vue_enabled?(project)
::Feature.enabled?(:ci_commit_pipeline_mini_graph_vue, project, default_enabled: :yaml)
end
def self.remove_duplicate_artifact_exposure_paths?(project)
::Feature.enabled?(:remove_duplicate_artifact_exposure_paths, project, default_enabled: :yaml)
end
......
......@@ -3449,9 +3449,6 @@ msgstr ""
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
msgid "An error occurred while fetching the builds."
msgstr ""
msgid "An error occurred while fetching the job log."
msgstr ""
......
......@@ -628,44 +628,6 @@ RSpec.describe Projects::PipelinesController do
end
end
describe 'GET stages_ajax.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when accessing existing stage' do
before do
create(:ci_build, pipeline: pipeline, stage: 'build')
get_stage_ajax('build')
end
it 'returns html source for stage dropdown' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('projects/pipelines/_stage')
expect(json_response).to include('html')
end
end
context 'when accessing unknown stage' do
before do
get_stage_ajax('test')
end
it 'responds with not found' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
def get_stage_ajax(name)
get :stage_ajax, params: {
namespace_id: project.namespace,
project_id: project,
id: pipeline.id,
stage: name
},
format: :json
end
end
describe 'GET status.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:status) { pipeline.detailed_status(double('user')) }
......
......@@ -23,7 +23,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js do
end
it 'displays a mini pipeline graph' do
expect(page).to have_selector('.mr-widget-pipeline-graph')
expect(page).to have_selector('[data-testid="pipeline-mini-graph"]')
end
context 'as json' do
......
......@@ -16,7 +16,6 @@ RSpec.describe 'Mini Pipeline Graph in Commit View', :js do
let(:build) { create(:ci_build, pipeline: pipeline, status: :running) }
shared_examples 'shows ci icon and mini pipeline' do
before do
build.run
visit project_commit_path(project, project.commit.id)
......@@ -27,7 +26,7 @@ RSpec.describe 'Mini Pipeline Graph in Commit View', :js do
end
it 'displays a mini pipeline graph' do
expect(page).to have_selector('.mr-widget-pipeline-graph')
expect(page).to have_selector('[data-testid="pipeline-mini-graph"]')
first('.mini-pipeline-graph-dropdown-toggle').click
......@@ -42,30 +41,13 @@ RSpec.describe 'Mini Pipeline Graph in Commit View', :js do
end
end
context 'when ci_commit_pipeline_mini_graph_vue is disabled' do
before do
stub_feature_flags(ci_commit_pipeline_mini_graph_vue: false)
end
it_behaves_like 'shows ci icon and mini pipeline'
end
context 'when ci_commit_pipeline_mini_graph_vue is enabled' do
before do
stub_feature_flags(ci_commit_pipeline_mini_graph_vue: true)
end
it_behaves_like 'shows ci icon and mini pipeline'
end
end
context 'when commit does not have pipelines' do
before do
visit project_commit_path(project, project.commit.id)
end
it 'does not display a mini pipeline graph' do
expect(page).not_to have_selector('.mr-widget-pipeline-graph')
expect(page).not_to have_selector('[data-testid="pipeline-mini-graph"]')
end
end
end
......@@ -534,7 +534,7 @@ RSpec.describe 'Pipelines', :js do
end
it 'renders a mini pipeline graph' do
expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]')
expect(page).to have_selector('[data-testid="pipeline-mini-graph"]')
expect(page).to have_selector(dropdown_selector)
end
......
<div class="js-builds-dropdown-tests dropdown dropdown" data-testid="widget-mini-pipeline-graph">
<button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
Dropdown
</button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<li class="js-builds-dropdown-list scrollable-menu">
<ul></ul>
</li>
<li class="js-builds-dropdown-loading hidden">
<span class="gl-spinner"></span>
</li>
</ul>
</div>
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
describe('Mini Pipeline Graph Dropdown', () => {
beforeEach(() => {
loadFixtures('static/mini_dropdown_graph.html');
});
describe('When is initialized', () => {
it('should initialize without errors when no options are given', () => {
const miniPipelineGraph = new MiniPipelineGraph();
expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container');
});
it('should set the container as the given prop', () => {
const container = '.foo';
const miniPipelineGraph = new MiniPipelineGraph({ container });
expect(miniPipelineGraph.container).toEqual(container);
});
});
describe('When dropdown is clicked', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('should call getBuildsList', () => {
const getBuildsListSpy = jest
.spyOn(MiniPipelineGraph.prototype, 'getBuildsList')
.mockImplementation(() => {});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
expect(getBuildsListSpy).toHaveBeenCalled();
});
it('should make a request to the endpoint provided in the html', () => {
const ajaxSpy = jest.spyOn(axios, 'get');
mock.onGet('foobar').reply(200, {
html: '',
});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
expect(ajaxSpy.mock.calls[0][0]).toEqual('foobar');
});
it('should not close when user uses cmd/ctrl + click', (done) => {
mock.onGet('foobar').reply(200, {
html: `<li>
<a class="mini-pipeline-graph-dropdown-item" href="#">
<span class="ci-status-icon ci-status-icon-failed"></span>
<span>build</span>
</a>
<a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
</li>`,
});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
waitForPromises()
.then(() => {
document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
})
.then(waitForPromises)
.then(() => {
expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
})
.then(done)
.catch(done.fail);
});
it('should close the dropdown when request returns an error', (done) => {
mock.onGet('foobar').networkError();
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
setImmediate(() => {
expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
done();
});
});
});
});
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { setHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import { loadBranches } from '~/projects/commit_box/info/load_branches';
......@@ -9,32 +10,38 @@ const mockBranchesRes =
describe('~/projects/commit_box/info/load_branches', () => {
let mock;
let el;
const getElInnerHtml = () => document.querySelector('.js-commit-box-info').innerHTML;
beforeEach(() => {
setHTMLFixture(`
<div class="js-commit-box-info" data-commit-path="${mockCommitPath}">
<div class="commit-info branches">
<span class="spinner"/>
</div>
</div>`);
mock = new MockAdapter(axios);
mock.onGet(mockCommitPath).reply(200, mockBranchesRes);
el = document.createElement('div');
el.dataset.commitPath = mockCommitPath;
el.innerHTML = '<div class="commit-info branches"><span class="spinner"/></div>';
});
it('loads and renders branches info', async () => {
loadBranches(el);
loadBranches();
await waitForPromises();
expect(el.innerHTML).toBe(`<div class="commit-info branches">${mockBranchesRes}</div>`);
expect(getElInnerHtml()).toMatchInterpolatedText(
`<div class="commit-info branches">${mockBranchesRes}</div>`,
);
});
it('does not load when no container is provided', async () => {
loadBranches(null);
loadBranches('.js-another-class');
await waitForPromises();
expect(mock.history.get).toHaveLength(0);
});
describe('when braches request returns unsafe content', () => {
describe('when branches request returns unsafe content', () => {
beforeEach(() => {
mock
.onGet(mockCommitPath)
......@@ -42,25 +49,25 @@ describe('~/projects/commit_box/info/load_branches', () => {
});
it('displays sanitized html', async () => {
loadBranches(el);
loadBranches();
await waitForPromises();
expect(el.innerHTML).toBe(
expect(getElInnerHtml()).toMatchInterpolatedText(
'<div class="commit-info branches"><a href="/-/commits/master">master</a></div>',
);
});
});
describe('when braches request fails', () => {
describe('when branches request fails', () => {
beforeEach(() => {
mock.onGet(mockCommitPath).reply(500, 'Error!');
});
it('attempts to load and renders an error', async () => {
loadBranches(el);
loadBranches();
await waitForPromises();
expect(el.innerHTML).toBe(
expect(getElInnerHtml()).toMatchInterpolatedText(
'<div class="commit-info branches">Failed to load branches. Please try again.</div>',
);
});
......
......@@ -34,14 +34,6 @@ RSpec.describe 'projects/commit/_commit_box.html.haml' do
expect(rendered).to have_selector('.js-commit-pipeline-mini-graph')
end
it 'shows pipeline stages in haml when feature flag is disabled' do
stub_feature_flags(ci_commit_pipeline_mini_graph_vue: false)
render
expect(rendered).to have_selector('.js-commit-pipeline-graph')
end
end
context 'when there are multiple pipelines for a commit' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'projects/pipelines/_stage' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:stage) { build(:ci_stage, pipeline: pipeline) }
before do
assign :stage, stage
end
context 'when there are only latest builds present' do
before do
create(:ci_build, name: 'test:build',
stage: stage.name,
pipeline: pipeline)
end
it 'shows the builds in the stage' do
render
expect(rendered).to have_text 'test:build'
end
end
context 'when build belongs to different stage' do
before do
create(:ci_build, name: 'test:build',
stage: 'other:stage',
pipeline: pipeline)
end
it 'does not render build' do
render
expect(rendered).not_to have_text 'test:build'
end
end
context 'when there are retried builds present' do
before do
create(:ci_build, name: 'test:build', stage: stage.name, pipeline: pipeline, retried: true)
create(:ci_build, name: 'test:build', stage: stage.name, pipeline: pipeline)
end
it 'shows only latest builds' do
render
expect(rendered).to have_text 'test:build', count: 1
end
end
context 'when there are multiple builds' do
before do
Ci::HasStatus::AVAILABLE_STATUSES.each do |status|
create_build(status)
end
end
it 'shows them in order' do
render
expect(rendered).to have_text(Ci::HasStatus::ORDERED_STATUSES.join(" "))
end
def create_build(status)
create(:ci_build, name: status, status: status,
pipeline: pipeline, stage: stage.name)
end
end
end
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