Commit 7a3e6fe3 authored by Mark Florian's avatar Mark Florian

Merge branch...

Merge branch '230749-tabs-haml-migrate-app-views-projects-pipelines-_with_tabs-html-haml-to-gltabs' into 'master'

Migrate pipeline tabs to Vue - Introduce base layout

See merge request gitlab-org/gitlab!80401
parents 98e8ece0 f862ca32
<script>
import { GlTabs, GlTab } from '@gitlab/ui';
import { __ } from '~/locale';
import PipelineGraphWrapper from './graph/graph_component_wrapper.vue';
import Dag from './dag/dag.vue';
import JobsApp from './jobs/jobs_app.vue';
import TestReports from './test_reports/test_reports.vue';
export default {
i18n: {
tabs: {
failedJobsTitle: __('Failed Jobs'),
jobsTitle: __('Jobs'),
needsTitle: __('Needs'),
pipelineTitle: __('Pipeline'),
testsTitle: __('Tests'),
},
},
components: {
Dag,
GlTab,
GlTabs,
JobsApp,
FailedJobsApp: JobsApp,
PipelineGraphWrapper,
TestReports,
},
};
</script>
<template>
<gl-tabs>
<gl-tab :title="$options.i18n.tabs.pipelineTitle" data-testid="pipeline-tab">
<pipeline-graph-wrapper />
</gl-tab>
<gl-tab :title="$options.i18n.tabs.needsTitle" data-testid="dag-tab">
<dag />
</gl-tab>
<gl-tab :title="$options.i18n.tabs.jobsTitle" data-testid="jobs-tab">
<jobs-app />
</gl-tab>
<gl-tab :title="$options.i18n.tabs.failedJobsTitle" data-testid="failed-jobs-tab">
<failed-jobs-app />
</gl-tab>
<gl-tab :title="$options.i18n.tabs.testsTitle" data-testid="tests-tab">
<test-reports />
</gl-tab>
<slot></slot>
</gl-tabs>
</template>
......@@ -13,6 +13,7 @@ const SELECTORS = {
PIPELINE_GRAPH: '#js-pipeline-graph-vue',
PIPELINE_HEADER: '#js-pipeline-header-vue',
PIPELINE_NOTIFICATION: '#js-pipeline-notification',
PIPELINE_TABS: '#js-pipeline-tabs',
PIPELINE_TESTS: '#js-pipeline-tests-detail',
PIPELINE_JOBS: '#js-pipeline-jobs-vue',
};
......@@ -28,22 +29,6 @@ export default async function initPipelineDetailsBundle() {
});
}
try {
createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset);
} catch {
createFlash({
message: __('An error occurred while loading the pipeline.'),
});
}
try {
createPipelineHeaderApp(SELECTORS.PIPELINE_HEADER, apolloProvider, dataset.graphqlResourceEtag);
} catch {
createFlash({
message: __('An error occurred while loading a section of this page.'),
});
}
try {
createPipelineNotificationApp(SELECTORS.PIPELINE_NOTIFICATION, apolloProvider);
} catch {
......@@ -52,27 +37,47 @@ export default async function initPipelineDetailsBundle() {
});
}
try {
createDagApp(apolloProvider);
} catch {
createFlash({
message: __('An error occurred while loading the Needs tab.'),
});
}
if (gon.features?.pipelineTabsVue) {
const { createPipelineTabs } = await import('./pipeline_tabs');
try {
createTestDetails(SELECTORS.PIPELINE_TESTS);
} catch {
createFlash({
message: __('An error occurred while loading the Test Reports tab.'),
});
}
try {
createPipelineTabs(SELECTORS.PIPELINE_TABS, apolloProvider);
} catch {
createFlash({
message: __('An error occurred while loading a section of this page.'),
});
}
} else {
try {
createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset);
} catch {
createFlash({
message: __('An error occurred while loading the pipeline.'),
});
}
try {
createPipelineJobsApp(SELECTORS.PIPELINE_JOBS);
} catch {
createFlash({
message: __('An error occurred while loading the Jobs tab.'),
});
try {
createDagApp(apolloProvider);
} catch {
createFlash({
message: __('An error occurred while loading the Needs tab.'),
});
}
try {
createTestDetails(SELECTORS.PIPELINE_TESTS);
} catch {
createFlash({
message: __('An error occurred while loading the Test Reports tab.'),
});
}
try {
createPipelineJobsApp(SELECTORS.PIPELINE_JOBS);
} catch {
createFlash({
message: __('An error occurred while loading the Jobs tab.'),
});
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import PipelineTabs from 'ee_else_ce/pipelines/components/pipeline_tabs.vue';
import { reportToSentry } from './utils';
Vue.use(VueApollo);
const createPipelineTabs = (selector, apolloProvider) => {
const el = document.querySelector(selector);
if (!el) return;
const { dataset } = document.querySelector(selector);
const {
canGenerateCodequalityReports,
codequalityReportDownloadPath,
downloadablePathForReportType,
exposeSecurityDashboard,
exposeLicenseScanningData,
} = dataset;
// eslint-disable-next-line no-new
new Vue({
el: selector,
components: {
PipelineTabs,
},
apolloProvider,
provide: {
canGenerateCodequalityReports: JSON.parse(canGenerateCodequalityReports),
codequalityReportDownloadPath,
downloadablePathForReportType,
exposeSecurityDashboard: JSON.parse(exposeSecurityDashboard),
exposeLicenseScanningData: JSON.parse(exposeLicenseScanningData),
},
errorCaptured(err, _vm, info) {
reportToSentry('pipeline_tabs', `error: ${err}, info: ${info}`);
},
render(createElement) {
return createElement(PipelineTabs);
},
});
};
export { createPipelineTabs };
......@@ -17,6 +17,10 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
before_action do
push_frontend_feature_flag(:pipeline_tabs_vue, @project, default_enabled: :yaml)
end
# Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/225596
before_action :redirect_for_legacy_scope_filter, only: [:index], if: -> { request.format.html? }
......
# frozen_string_literal: true
module Projects
module PipelineHelper
def js_pipeline_tabs_data(project, pipeline)
{
can_generate_codequality_reports: pipeline.can_generate_codequality_reports?.to_json,
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json),
pipeline_project_path: project.full_path
}
end
end
end
Projects::PipelineHelper.prepend_mod
......@@ -27,6 +27,8 @@
= s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe }
#js-pipeline-notification{ data: { deprecated_keywords_doc_path: help_page_path('ci/yaml/index.md', anchor: 'deprecated-keywords'), full_path: @project.full_path, pipeline_iid: @pipeline.iid } }
= render "projects/pipelines/with_tabs", pipeline: @pipeline, stages: @stages, pipeline_has_errors: pipeline_has_errors
- if Feature.enabled?(:pipeline_tabs_vue, @project, default_enabled: :yaml)
#js-pipeline-tabs{ data: js_pipeline_tabs_data(@project, @pipeline) }
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline, stages: @stages, pipeline_has_errors: pipeline_has_errors
.js-pipeline-details-vue{ data: { metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline) } }
---
name: pipeline_tabs_vue
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80401
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353118
milestone: '14.10'
type: development
group: group::pipeline authoring
default_enabled: false
<script>
import { GlTab } from '@gitlab/ui';
import BasePipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __ } from '~/locale';
import CodequalityReportApp from 'ee/codequality_report/codequality_report.vue';
import CodequalityReportAppGraphql from 'ee/codequality_report/codequality_report_graphql.vue';
import LicenseComplianceApp from 'ee/license_compliance/components/app.vue';
import PipelineSecurityDashboard from 'ee/security_dashboard/components/pipeline/pipeline_security_dashboard.vue';
export default {
i18n: {
tabs: {
securityTitle: __('Security'),
licenseTitle: __('Licenses'),
codeQualityTitle: __('Code Quality'),
},
},
components: {
BasePipelineTabs,
CodequalityReportApp,
CodequalityReportAppGraphql,
GlTab,
LicenseComplianceApp,
PipelineSecurityDashboard,
},
mixins: [glFeatureFlagMixin()],
inject: [
'canGenerateCodequalityReports',
'codequalityReportDownloadPath',
'exposeSecurityDashboard',
'exposeLicenseScanningData',
],
computed: {
isGraphqlCodeQuality() {
return this.glFeatures.graphqlCodeQualityFullReport;
},
showCodeQualityTab() {
return Boolean(this.codequalityReportDownloadPath || this.canGenerateCodequalityReports);
},
showLicenseTab() {
return Boolean(this.exposeLicenseScanningData);
},
showSecurityTab() {
return Boolean(this.exposeSecurityDashboard);
},
},
};
</script>
<template>
<base-pipeline-tabs>
<gl-tab
v-if="showSecurityTab"
:title="$options.i18n.tabs.securityTitle"
data-testid="security-tab"
>
<pipeline-security-dashboard />
</gl-tab>
<gl-tab
v-if="showLicenseTab"
:title="$options.i18n.tabs.licenseTitle"
data-testid="license-tab"
>
<license-compliance-app />
</gl-tab>
<gl-tab
v-if="showCodeQualityTab"
:title="$options.i18n.tabs.codeQualityTitle"
data-testid="code-quality-tab"
data-track-action="click_button"
data-track-label="get_codequality_report"
>
<codequality-report-app-graphql v-if="isGraphqlCodeQuality" />
<codequality-report-app v-else />
</gl-tab>
</base-pipeline-tabs>
</template>
# frozen_string_literal: true
module EE
module Projects
module PipelineHelper
extend ::Gitlab::Utils::Override
override :js_pipeline_tabs_data
def js_pipeline_tabs_data(project, pipeline)
super.merge(
codequality_report_download_path: codequality_report_download_path(project, pipeline),
expose_license_scanning_data: pipeline.expose_license_scanning_data?.to_json,
expose_security_dashboard: pipeline.expose_security_dashboard?.to_json
)
end
def codequality_report_download_path(project, pipeline)
return unless project.licensed_feature_available?(:full_codequality_report)
pipeline.downloadable_path_for_report_type(:codequality)
end
end
end
end
......@@ -14,6 +14,10 @@ RSpec.describe Projects::PipelinesController do
end
describe 'GET security' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
context 'with a sast artifact' do
before do
create(:ee_ci_build, :sast, pipeline: pipeline)
......@@ -44,6 +48,10 @@ RSpec.describe Projects::PipelinesController do
end
context 'without sast artifact' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
context 'with feature enabled' do
before do
stub_licensed_features(sast: true)
......@@ -69,6 +77,10 @@ RSpec.describe Projects::PipelinesController do
end
describe 'GET licenses' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
let(:licenses_with_html) {get :licenses, format: :html, params: { namespace_id: project.namespace, project_id: project, id: pipeline }}
let(:licenses_with_json) {get :licenses, format: :json, params: { namespace_id: project.namespace, project_id: project, id: pipeline }}
let!(:mit_license) { create(:software_license, :mit) }
......
......@@ -14,6 +14,10 @@ RSpec.describe 'Pipeline', :js do
end
describe 'GET /:project/-/pipelines/:id' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
let(:pipeline) { create(:ci_pipeline, :with_job, project: project, ref: 'master', sha: project.commit.id, user: user) }
subject { visit project_pipeline_path(project, pipeline) }
......@@ -133,6 +137,7 @@ RSpec.describe 'Pipeline', :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
stub_feature_flags(pipeline_tabs_vue: false)
stub_licensed_features(sast: true, security_dashboard: true)
stub_feature_flags(pipeline_security_dashboard_graphql: false)
end
......@@ -170,6 +175,7 @@ RSpec.describe 'Pipeline', :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
stub_feature_flags(pipeline_tabs_vue: false)
stub_licensed_features(license_scanning: true)
end
......@@ -205,6 +211,10 @@ RSpec.describe 'Pipeline', :js do
end
describe 'GET /:project/-/pipelines/:id/codequality_report', :aggregate_failures do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
shared_examples_for 'full codequality report' do
context 'when licensed' do
before do
......
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BasePipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
import PipelineTabs from 'ee/pipelines/components/pipeline_tabs.vue';
import CodequalityReportApp from 'ee/codequality_report/codequality_report.vue';
import CodequalityReportAppGraphql from 'ee/codequality_report/codequality_report_graphql.vue';
import LicenseComplianceApp from 'ee/license_compliance/components/app.vue';
import PipelineSecurityDashboard from 'ee/security_dashboard/components/pipeline/pipeline_security_dashboard.vue';
describe('The Pipeline Tabs', () => {
let wrapper;
const findCodeQualityTab = () => wrapper.findByTestId('code-quality-tab');
const findDagTab = () => wrapper.findByTestId('dag-tab');
const findFailedJobsTab = () => wrapper.findByTestId('failed-jobs-tab');
const findJobsTab = () => wrapper.findByTestId('jobs-tab');
const findLicenseTab = () => wrapper.findByTestId('license-tab');
const findPipelineTab = () => wrapper.findByTestId('pipeline-tab');
const findSecurityTab = () => wrapper.findByTestId('security-tab');
const findTestsTab = () => wrapper.findByTestId('tests-tab');
const findCodeQualityApp = () => wrapper.findComponent(CodequalityReportApp);
const findCodeQualityAppGraphql = () => wrapper.findComponent(CodequalityReportAppGraphql);
const findLicenseApp = () => wrapper.findComponent(LicenseComplianceApp);
const findSecurityApp = () => wrapper.findComponent(PipelineSecurityDashboard);
const defaultProvide = {
canGenerateCodequalityReports: false,
codequalityReportDownloadPath: '',
exposeSecurityDashboard: false,
exposeLicenseScanningData: false,
};
const createComponent = ({ propsData = {}, provide = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(PipelineTabs, {
propsData,
provide: {
...defaultProvide,
...provide,
},
stubs: {
BasePipelineTabs,
Dag: { template: '<div id="dag"/>' },
JobsApp: { template: '<div class="jobs" />' },
PipelineGraph: { template: '<div id="graph" />' },
TestReports: { template: '<div id="tests" />' },
},
}),
);
};
afterEach(() => {
wrapper.destroy();
});
// The failed jobs MUST be removed from here and tested individually once
// the logic for the tab is implemented.
describe('CE Tabs', () => {
it.each`
tabName | tabComponent
${'Pipeline'} | ${findPipelineTab}
${'Dag'} | ${findDagTab}
${'Failed Jobs'} | ${findFailedJobsTab}
${'Jobs'} | ${findJobsTab}
${'Tests'} | ${findTestsTab}
`('shows $tabName tab', ({ tabComponent }) => {
createComponent();
expect(tabComponent().exists()).toBe(true);
});
});
describe('EE Tabs', () => {
describe('visibility', () => {
it.each`
tabName | tabComponent | appComponent | provideKey | isVisible | text
${'Security'} | ${findSecurityTab} | ${findSecurityApp} | ${'exposeSecurityDashboard'} | ${true} | ${'shows'}
${'Security'} | ${findSecurityTab} | ${findSecurityApp} | ${'exposeSecurityDashboard'} | ${false} | ${'hides'}
${'License'} | ${findLicenseTab} | ${findLicenseApp} | ${'exposeLicenseScanningData'} | ${true} | ${'shows'}
${'License'} | ${findLicenseTab} | ${findLicenseApp} | ${'exposeLicenseScanningData'} | ${false} | ${'hides'}
`(
'$text $tabName and its associated component when $provideKey is $provideKey ',
({ tabComponent, appComponent, provideKey, isVisible }) => {
createComponent({
provide: { [provideKey]: isVisible },
});
expect(tabComponent().exists()).toBe(isVisible);
expect(appComponent().exists()).toBe(isVisible);
},
);
});
describe('code quality visibility', () => {
describe('feature flags', () => {
describe('with `graphqlCodeQualityFullReport` enabled', () => {
beforeEach(() => {
createComponent({
provide: {
canGenerateCodequalityReports: true,
glFeatures: {
graphqlCodeQualityFullReport: true,
},
},
});
});
it('shows the graphql code quality report app', () => {
expect(findCodeQualityAppGraphql().exists()).toBe(true);
expect(findCodeQualityApp().exists()).toBe(false);
});
});
describe('with `graphqlCodeQualityFullReport` disabled', () => {
beforeEach(() => {
createComponent({
provide: {
canGenerateCodequalityReports: true,
glFeatures: {
graphqlCodeQualityFullReport: false,
},
},
});
});
it('shows the default code quality report app', () => {
expect(findCodeQualityAppGraphql().exists()).toBe(false);
expect(findCodeQualityApp().exists()).toBe(true);
});
});
});
it.each`
provideValue | isVisible | codequalityReportDownloadPath | text
${true} | ${true} | ${''} | ${'shows'}
${false} | ${false} | ${''} | ${'hides'}
${false} | ${true} | ${'/path'} | ${'shows'}
${true} | ${true} | ${'/path'} | ${'shows'}
`(
'$text Code Quality and its associated component when canGenerateCodequalityReports is $provideValue and codequalityReportDownloadPath is $codequalityReportDownloadPath',
({ provideValue, isVisible, codequalityReportDownloadPath }) => {
createComponent({
provide: { canGenerateCodequalityReports: provideValue, codequalityReportDownloadPath },
});
expect(findCodeQualityTab().exists()).toBe(isVisible);
expect(findCodeQualityApp().exists()).toBe(isVisible);
},
);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::PipelineHelper do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:raw_pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
let_it_be(:pipeline) { Ci::PipelinePresenter.new(raw_pipeline, current_user: user)}
describe '#js_pipeline_tabs_data' do
subject(:pipeline_tabs_data) { helper.js_pipeline_tabs_data(project, pipeline) }
it 'returns pipeline tabs data' do
expect(pipeline_tabs_data).to eq({
can_generate_codequality_reports: pipeline.can_generate_codequality_reports?.to_json,
codequality_report_download_path: helper.codequality_report_download_path(project, pipeline),
expose_license_scanning_data: pipeline.expose_license_scanning_data?.to_json,
expose_security_dashboard: pipeline.expose_security_dashboard?.to_json,
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json),
pipeline_project_path: project.full_path
})
end
end
describe 'codequality_report_download_path' do
before do
project.add_developer(user)
end
subject(:codequality_report_path) { helper.codequality_report_download_path(project, pipeline) }
describe 'when `full_codequality_report` feature is not available' do
before do
stub_licensed_features(full_codequality_report: false)
end
it 'returns nil' do
is_expected.to be(nil)
end
end
describe 'when `full_code_quality_report` feature is available' do
before do
stub_licensed_features(full_codequality_report: true)
end
describe 'and there is no artefact for codequality' do
it 'returns nil for `codequality`' do
is_expected.to be(nil)
end
end
describe 'and there is an artefact for codequality' do
before do
create(:ci_build, :codequality_report, pipeline: raw_pipeline)
end
it 'returns the downloadable path for `codequality`' do
is_expected.not_to be(nil)
is_expected.to eq(pipeline.downloadable_path_for_report_type(:codequality))
end
end
end
end
end
......@@ -10,6 +10,7 @@ RSpec.describe 'Commits' do
before do
sign_in(user)
stub_ci_pipeline_to_return_yaml_file
stub_feature_flags(pipeline_tabs_vue: false)
end
let(:creator) { create(:user, developer_projects: [project]) }
......@@ -93,6 +94,7 @@ RSpec.describe 'Commits' do
context 'Download artifacts', :js do
before do
stub_feature_flags(pipeline_tabs_vue: false)
create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
end
......@@ -122,6 +124,7 @@ RSpec.describe 'Commits' do
context "when logged as reporter", :js do
before do
stub_feature_flags(pipeline_tabs_vue: false)
project.add_reporter(user)
create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit builds_project_pipeline_path(project, pipeline)
......
......@@ -15,6 +15,7 @@ RSpec.describe 'Pipeline', :js do
before do
sign_in(user)
project.add_role(user, role)
stub_feature_flags(pipeline_tabs_vue: false)
end
shared_context 'pipeline builds' do
......@@ -356,6 +357,7 @@ RSpec.describe 'Pipeline', :js do
context 'page tabs' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
visit_pipeline
end
......@@ -388,6 +390,7 @@ RSpec.describe 'Pipeline', :js do
let(:pipeline) { create(:ci_pipeline, :with_test_reports, :with_report_results, project: project) }
before do
stub_feature_flags(pipeline_tabs_vue: false)
visit_pipeline
wait_for_requests
end
......@@ -924,6 +927,7 @@ RSpec.describe 'Pipeline', :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
stub_feature_flags(pipeline_tabs_vue: false)
visit builds_project_pipeline_path(project, pipeline)
end
......@@ -944,6 +948,10 @@ RSpec.describe 'Pipeline', :js do
end
context 'page tabs' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
it 'shows Pipeline, Jobs and DAG tabs with link' do
expect(page).to have_link('Pipeline')
expect(page).to have_link('Jobs')
......@@ -1014,6 +1022,10 @@ RSpec.describe 'Pipeline', :js do
end
describe 'GET /:project/-/pipelines/:id/failures' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: '1234') }
let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
......@@ -1139,6 +1151,7 @@ RSpec.describe 'Pipeline', :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
stub_feature_flags(pipeline_tabs_vue: false)
visit dag_project_pipeline_path(project, pipeline)
end
......
......@@ -623,6 +623,7 @@ RSpec.describe 'Pipelines', :js do
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3, ref: 'master')
stub_feature_flags(pipeline_tabs_vue: false)
visit project_pipeline_path(project, pipeline)
wait_for_requests
end
......
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
import Dag from '~/pipelines/components/dag/dag.vue';
import JobsApp from '~/pipelines/components/jobs/jobs_app.vue';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
describe('The Pipeline Tabs', () => {
let wrapper;
const findDagTab = () => wrapper.findByTestId('dag-tab');
const findFailedJobsTab = () => wrapper.findByTestId('failed-jobs-tab');
const findJobsTab = () => wrapper.findByTestId('jobs-tab');
const findPipelineTab = () => wrapper.findByTestId('pipeline-tab');
const findTestsTab = () => wrapper.findByTestId('tests-tab');
const findDagApp = () => wrapper.findComponent(Dag);
const findFailedJobsApp = () => wrapper.findComponent(JobsApp);
const findJobsApp = () => wrapper.findComponent(JobsApp);
const findPipelineApp = () => wrapper.findComponent(PipelineGraphWrapper);
const findTestsApp = () => wrapper.findComponent(TestReports);
const createComponent = (propsData = {}) => {
wrapper = extendedWrapper(
shallowMount(PipelineTabs, {
propsData,
stubs: {
Dag: { template: '<div id="dag"/>' },
JobsApp: { template: '<div class="jobs" />' },
PipelineGraph: { template: '<div id="graph" />' },
TestReports: { template: '<div id="tests" />' },
},
}),
);
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
// The failed jobs MUST be removed from here and tested individually once
// the logic for the tab is implemented.
describe('Tabs', () => {
it.each`
tabName | tabComponent | appComponent
${'Pipeline'} | ${findPipelineTab} | ${findPipelineApp}
${'Dag'} | ${findDagTab} | ${findDagApp}
${'Jobs'} | ${findJobsTab} | ${findJobsApp}
${'Failed Jobs'} | ${findFailedJobsTab} | ${findFailedJobsApp}
${'Tests'} | ${findTestsTab} | ${findTestsApp}
`('shows $tabName tab and its associated component', ({ appComponent, tabComponent }) => {
expect(tabComponent().exists()).toBe(true);
expect(appComponent().exists()).toBe(true);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::PipelineHelper do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:raw_pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
let_it_be(:pipeline) { Ci::PipelinePresenter.new(raw_pipeline, current_user: user)}
describe '#js_pipeline_tabs_data' do
subject(:pipeline_tabs_data) { helper.js_pipeline_tabs_data(project, pipeline) }
it 'returns pipeline tabs data' do
expect(pipeline_tabs_data).to include({
can_generate_codequality_reports: pipeline.can_generate_codequality_reports?.to_json,
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json),
pipeline_project_path: project.full_path
})
end
end
end
......@@ -13,6 +13,7 @@ RSpec.describe 'projects/pipelines/show' do
before do
assign(:project, project)
assign(:pipeline, presented_pipeline)
stub_feature_flags(pipeline_tabs_vue: false)
end
context 'when pipeline has errors' do
......
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