Commit d362c451 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Etienne Baqué

Disable multi-project viz for free users

There was a bug where we would allow free tier
users to see multi project pipelines but this is
a premium feature. We now disable the expand
button for free tier users and show a popover
explaining why this is disabled and adding tests
to ensure it works as expected and no regression
occurs again.

Changelog: changed
parent 594ff713
...@@ -92,6 +92,9 @@ export default { ...@@ -92,6 +92,9 @@ export default {
hasUpstreamPipelines() { hasUpstreamPipelines() {
return Boolean(this.pipeline?.upstream?.length > 0); return Boolean(this.pipeline?.upstream?.length > 0);
}, },
isMultiProjectVizAvailable() {
return Boolean(this.pipeline?.user?.namespace?.crossProjectPipelineAvailable);
},
isStageView() { isStageView() {
return this.viewType === STAGE_VIEW; return this.viewType === STAGE_VIEW;
}, },
...@@ -178,6 +181,7 @@ export default { ...@@ -178,6 +181,7 @@ export default {
<linked-pipelines-column <linked-pipelines-column
v-if="showUpstreamPipelines" v-if="showUpstreamPipelines"
:config-paths="configPaths" :config-paths="configPaths"
:is-multi-project-viz-available="isMultiProjectVizAvailable"
:linked-pipelines="upstreamPipelines" :linked-pipelines="upstreamPipelines"
:column-title="__('Upstream')" :column-title="__('Upstream')"
:show-links="showJobLinks" :show-links="showJobLinks"
...@@ -226,6 +230,7 @@ export default { ...@@ -226,6 +230,7 @@ export default {
v-if="showDownstreamPipelines" v-if="showDownstreamPipelines"
class="gl-mr-6" class="gl-mr-6"
:config-paths="configPaths" :config-paths="configPaths"
:is-multi-project-viz-available="isMultiProjectVizAvailable"
:linked-pipelines="downstreamPipelines" :linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')" :column-title="__('Downstream')"
:show-links="showJobLinks" :show-links="showJobLinks"
......
<script> <script>
import { GlBadge, GlButton, GlLink, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import {
GlBadge,
GlButton,
GlLink,
GlLoadingIcon,
GlPopover,
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import TierBadge from '~/vue_shared/components/tier_badge.vue';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import CiStatus from '~/vue_shared/components/ci_icon.vue'; import CiStatus from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils'; import { reportToSentry } from '../../utils';
import { DOWNSTREAM, UPSTREAM } from './constants'; import { DOWNSTREAM, UPSTREAM } from './constants';
export default { export default {
i18n: {
popover: {
title: s__('Pipelines|Multi-project pipeline graphs'),
description: s__(
'Pipelines|Gitlab Premium users have access to the multi-project pipeline graph to improve the visualization of these pipelines. %{linkStart}Learn More%{linkEnd}',
),
},
},
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
...@@ -16,7 +33,11 @@ export default { ...@@ -16,7 +33,11 @@ export default {
GlButton, GlButton,
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlPopover,
GlSprintf,
TierBadge,
}, },
inject: ['multiProjectHelpPath'],
props: { props: {
columnTitle: { columnTitle: {
type: String, type: String,
...@@ -26,6 +47,10 @@ export default { ...@@ -26,6 +47,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isMultiProjectVizAvailable: {
type: Boolean,
required: true,
},
isLoading: { isLoading: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -90,6 +115,9 @@ export default { ...@@ -90,6 +115,9 @@ export default {
pipelineStatus() { pipelineStatus() {
return this.pipeline.status; return this.pipeline.status;
}, },
popoverContainerId() {
return `popoverContainer-${this.pipeline.id}`;
},
projectName() { projectName() {
return this.pipeline.project.name; return this.pipeline.project.name;
}, },
...@@ -128,16 +156,21 @@ export default { ...@@ -128,16 +156,21 @@ export default {
<template> <template>
<div <div
ref="linkedPipeline"
v-gl-tooltip
class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1" class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
:class="flexDirection" :class="flexDirection"
:title="tooltipText"
data-qa-selector="child_pipeline" data-qa-selector="child_pipeline"
data-testid="linkedPipeline"
@mouseover="onDownstreamHovered" @mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave" @mouseleave="onDownstreamHoverLeave"
> >
<div class="gl-w-full gl-bg-white gl-p-3" :class="cardSpacingClass"> <div
v-gl-tooltip
class="gl-w-full gl-bg-white gl-p-3"
:class="cardSpacingClass"
data-testid="linkedPipelineBody"
data-qa-selector="linked_pipeline_body"
:title="tooltipText"
>
<div class="gl-display-flex gl-pr-3"> <div class="gl-display-flex gl-pr-3">
<ci-status <ci-status
v-if="!pipelineIsLoading" v-if="!pipelineIsLoading"
...@@ -163,17 +196,38 @@ export default { ...@@ -163,17 +196,38 @@ export default {
</gl-badge> </gl-badge>
</div> </div>
</div> </div>
<div class="gl-display-flex"> <div :id="popoverContainerId" class="gl-display-flex">
<gl-button <gl-button
:id="buttonId" :id="buttonId"
class="gl-shadow-none! gl-rounded-0!" class="gl-shadow-none! gl-rounded-0!"
:class="`js-pipeline-expand-${pipeline.id} ${buttonBorderClass}`" :class="`js-pipeline-expand-${pipeline.id} ${buttonBorderClass}`"
:icon="expandedIcon" :icon="expandedIcon"
:aria-label="__('Expand pipeline')" :aria-label="__('Expand pipeline')"
:disabled="!isMultiProjectVizAvailable"
data-testid="expand-pipeline-button" data-testid="expand-pipeline-button"
data-qa-selector="expand_pipeline_button" data-qa-selector="expand_pipeline_button"
@click="onClickLinkedPipeline" @click="onClickLinkedPipeline"
/> />
<gl-popover
v-if="!isMultiProjectVizAvailable"
placement="top"
:target="popoverContainerId"
triggers="hover"
>
<template #title>
<b>{{ $options.i18n.popover.title }}</b>
<tier-badge class="gl-mt-3" tier="premium"
/></template>
<p class="gl-my-0">
<gl-sprintf :message="$options.i18n.popover.description">
<template #link="{ content }"
><gl-link :href="multiProjectHelpPath" class="gl-font-sm" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
</gl-popover>
</div> </div>
</div> </div>
</template> </template>
...@@ -28,6 +28,10 @@ export default { ...@@ -28,6 +28,10 @@ export default {
required: true, required: true,
validator: validateConfigPaths, validator: validateConfigPaths,
}, },
isMultiProjectVizAvailable: {
type: Boolean,
required: true,
},
linkedPipelines: { linkedPipelines: {
type: Array, type: Array,
required: true, required: true,
...@@ -208,6 +212,7 @@ export default { ...@@ -208,6 +212,7 @@ export default {
<linked-pipeline <linked-pipeline
class="gl-display-inline-block" class="gl-display-inline-block"
:is-loading="isLoadingPipeline(pipeline.id)" :is-loading="isLoadingPipeline(pipeline.id)"
:is-multi-project-viz-available="isMultiProjectVizAvailable"
:pipeline="pipeline" :pipeline="pipeline"
:column-title="columnTitle" :column-title="columnTitle"
:type="type" :type="type"
......
...@@ -8,7 +8,7 @@ Vue.use(VueApollo); ...@@ -8,7 +8,7 @@ Vue.use(VueApollo);
const createPipelinesDetailApp = ( const createPipelinesDetailApp = (
selector, selector,
apolloProvider, apolloProvider,
{ pipelineProjectPath, pipelineIid, metricsPath, graphqlResourceEtag } = {}, { pipelineProjectPath, pipelineIid, metricsPath, graphqlResourceEtag, multiProjectHelpPath } = {},
) => { ) => {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
...@@ -22,6 +22,7 @@ const createPipelinesDetailApp = ( ...@@ -22,6 +22,7 @@ const createPipelinesDetailApp = (
pipelineProjectPath, pipelineProjectPath,
pipelineIid, pipelineIid,
graphqlResourceEtag, graphqlResourceEtag,
multiProjectHelpPath,
}, },
errorCaptured(err, _vm, info) { errorCaptured(err, _vm, info) {
reportToSentry('pipeline_details_graph', `error: ${err}, info: ${info}`); reportToSentry('pipeline_details_graph', `error: ${err}, info: ${info}`);
......
<script>
import { GlBadge, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
const gitlabTiers = {
free: s__('GitlabTiers|Free'),
premium: s__('GitlabTiers|Premium'),
ultimate: s__('GitlabTiers|Ultimate'),
};
export default {
components: {
GlBadge,
GlIcon,
},
props: {
tier: {
type: String,
required: true,
validator: (value) => Object.keys(gitlabTiers).includes(value),
},
size: {
type: String,
required: false,
default: 'md',
},
},
computed: {
tierName() {
return gitlabTiers[this.tier];
},
},
};
</script>
<template>
<gl-badge :size="size" class="gl-text-purple-600! gl-font-weight-bold gl-bg-purple-50!">
<gl-icon name="license" /> {{ tierName }}
</gl-badge>
</template>
...@@ -47,6 +47,15 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) { ...@@ -47,6 +47,15 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
id id
iid iid
complete complete
user {
__typename
id
namespace {
__typename
id
crossProjectPipelineAvailable
}
}
usesNeeds usesNeeds
userPermissions { userPermissions {
updatePipeline updatePipeline
......
# frozen_string_literal: true
module Projects
module PipelineHelper
def js_pipeline_details_data(project, pipeline)
{
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),
multi_project_help_path: help_page_path('ci/pipelines/multi_project_pipelines.md', anchor: 'multi-project-pipeline-visualization'),
pipeline_iid: pipeline.iid,
pipeline_project_path: project.full_path
}
end
end
end
...@@ -29,4 +29,4 @@ ...@@ -29,4 +29,4 @@
#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 } } #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 = 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) } } .js-pipeline-details-vue{ data: js_pipeline_details_data(@project, @pipeline) }
...@@ -30,7 +30,7 @@ RSpec.describe 'Pipeline', :js do ...@@ -30,7 +30,7 @@ RSpec.describe 'Pipeline', :js do
create_link(pipeline, downstream_pipeline) create_link(pipeline, downstream_pipeline)
end end
context 'expands the upstream pipeline on click' do context 'upstream pipelines' do
it 'renders upstream pipeline' do it 'renders upstream pipeline' do
subject subject
...@@ -38,59 +38,90 @@ RSpec.describe 'Pipeline', :js do ...@@ -38,59 +38,90 @@ RSpec.describe 'Pipeline', :js do
expect(page).to have_content(upstream_pipeline.project.name) expect(page).to have_content(upstream_pipeline.project.name)
end end
it 'expands the upstream on click' do context 'without license' do
subject it 'cannot expand the upstream pipeline' do
subject
page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click
wait_for_requests wait_for_requests
expect(page).to have_selector("#pipeline-links-container-#{upstream_pipeline.id}") expect(page).not_to have_selector("#pipeline-links-container-#{upstream_pipeline.id}")
end
end end
it 'closes the expanded upstream on click' do context 'with license' do
subject before do
stub_licensed_features(cross_project_pipelines: true)
end
# open it 'expands the upstream on click' do
page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click subject
wait_for_requests
# close page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click
page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click wait_for_requests
expect(page).to have_selector("#pipeline-links-container-#{upstream_pipeline.id}")
end
expect(page).not_to have_selector("#pipeline-links-container-#{upstream_pipeline.id}") it 'closes the expanded upstream on click' do
end subject
end
# open
page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click
wait_for_requests
it 'renders downstream pipeline' do # close
subject page.find(".js-pipeline-expand-#{upstream_pipeline.id}").click
expect(page).to have_content(downstream_pipeline.id) expect(page).not_to have_selector("#pipeline-links-container-#{upstream_pipeline.id}")
expect(page).to have_content(downstream_pipeline.project.name) end
end
end end
context 'expands the downstream pipeline on click' do context 'downstream pipelines' do
it 'expands the downstream on click' do it 'renders downstream pipeline' do
subject subject
page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click expect(page).to have_content(downstream_pipeline.id)
wait_for_requests expect(page).to have_content(downstream_pipeline.project.name)
expect(page).to have_selector("#pipeline-links-container-#{downstream_pipeline.id}")
end end
it 'closes the expanded downstream on click' do context 'without license' do
subject it 'cannot expand the downstream pipeline' do
subject
page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click
wait_for_requests
expect(page).not_to have_selector("#pipeline-links-container-#{downstream_pipeline.id}")
end
end
# open context 'with license' do
page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click before do
wait_for_requests stub_licensed_features(cross_project_pipelines: true)
end
# close it 'expands the downstream on click' do
page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click subject
expect(page).not_to have_selector("#pipeline-links-container-#{downstream_pipeline.id}") page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click
wait_for_requests
expect(page).to have_selector("#pipeline-links-container-#{downstream_pipeline.id}")
end
it 'closes the expanded downstream on click' do
subject
# open
page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click
wait_for_requests
# close
page.find(".js-pipeline-expand-#{downstream_pipeline.id}").click
expect(page).not_to have_selector("#pipeline-links-container-#{downstream_pipeline.id}")
end
end end
end end
end end
context 'when :ci_require_credit_card_on_free_plan flag is on' do context 'when :ci_require_credit_card_on_free_plan flag is on' do
before do before do
allow(::Gitlab).to receive(:com?).and_return(true) allow(::Gitlab).to receive(:com?).and_return(true)
......
...@@ -16771,6 +16771,15 @@ msgstr "" ...@@ -16771,6 +16771,15 @@ msgstr ""
msgid "GithubIntegration|This requires mirroring your GitHub repository to this project. %{docs_link}" msgid "GithubIntegration|This requires mirroring your GitHub repository to this project. %{docs_link}"
msgstr "" msgstr ""
msgid "GitlabTiers|Free"
msgstr ""
msgid "GitlabTiers|Premium"
msgstr ""
msgid "GitlabTiers|Ultimate"
msgstr ""
msgid "Gitpod" msgid "Gitpod"
msgstr "" msgstr ""
...@@ -26984,6 +26993,9 @@ msgstr "" ...@@ -26984,6 +26993,9 @@ msgstr ""
msgid "Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. There are active runners available to run your jobs right now. If you prefer, you can %{settingsLinkStart}configure your runners%{settingsLinkEnd} or %{docsLinkStart}learn more%{docsLinkEnd} about runners." msgid "Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. There are active runners available to run your jobs right now. If you prefer, you can %{settingsLinkStart}configure your runners%{settingsLinkEnd} or %{docsLinkStart}learn more%{docsLinkEnd} about runners."
msgstr "" msgstr ""
msgid "Pipelines|Gitlab Premium users have access to the multi-project pipeline graph to improve the visualization of these pipelines. %{linkStart}Learn More%{linkEnd}"
msgstr ""
msgid "Pipelines|If you are unsure, please ask a project maintainer to review it for you." msgid "Pipelines|If you are unsure, please ask a project maintainer to review it for you."
msgstr "" msgstr ""
...@@ -27023,6 +27035,9 @@ msgstr "" ...@@ -27023,6 +27035,9 @@ msgstr ""
msgid "Pipelines|More Information" msgid "Pipelines|More Information"
msgstr "" msgstr ""
msgid "Pipelines|Multi-project pipeline graphs"
msgstr ""
msgid "Pipelines|No runners detected" msgid "Pipelines|No runners detected"
msgstr "" msgstr ""
......
...@@ -24,6 +24,7 @@ module QA ...@@ -24,6 +24,7 @@ module QA
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
element :expand_pipeline_button element :expand_pipeline_button
element :child_pipeline element :child_pipeline
element :linked_pipeline_body
end end
view 'app/assets/javascripts/reports/components/report_section.vue' do view 'app/assets/javascripts/reports/components/report_section.vue' do
...@@ -93,7 +94,7 @@ module QA ...@@ -93,7 +94,7 @@ module QA
end end
def find_child_pipeline_by_title(title) def find_child_pipeline_by_title(title)
child_pipelines.find { |pipeline| pipeline[:title].include?(title) } find_element(:child_pipeline, text: title)
end end
def expand_child_pipeline(title: nil) def expand_child_pipeline(title: nil)
......
...@@ -42,7 +42,7 @@ module QA ...@@ -42,7 +42,7 @@ module QA
[upstream_project, downstream_project].each(&:remove_via_api!) [upstream_project, downstream_project].each(&:remove_via_api!)
end end
it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348088' do it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/353964' do
Page::Project::Pipeline::Show.perform do |parent_pipeline| Page::Project::Pipeline::Show.perform do |parent_pipeline|
Support::Waiter.wait_until { parent_pipeline.has_child_pipeline? } Support::Waiter.wait_until { parent_pipeline.has_child_pipeline? }
parent_pipeline.expand_child_pipeline parent_pipeline.expand_child_pipeline
......
...@@ -66,7 +66,6 @@ describe('graph component', () => { ...@@ -66,7 +66,6 @@ describe('graph component', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('with data', () => { describe('with data', () => {
......
import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlLoadingIcon, GlPopover } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants'; import { UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue'; import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
...@@ -9,15 +9,20 @@ import mockPipeline from './linked_pipelines_mock_data'; ...@@ -9,15 +9,20 @@ import mockPipeline from './linked_pipelines_mock_data';
describe('Linked pipeline', () => { describe('Linked pipeline', () => {
let wrapper; let wrapper;
const defaultProps = {
pipeline: mockPipeline,
columnTitle: 'Downstream',
type: DOWNSTREAM,
expanded: false,
isLoading: false,
isMultiProjectVizAvailable: true,
};
const downstreamProps = { const downstreamProps = {
pipeline: { pipeline: {
...mockPipeline, ...mockPipeline,
multiproject: false, multiproject: false,
}, },
columnTitle: 'Downstream',
type: DOWNSTREAM,
expanded: false,
isLoading: false,
}; };
const upstreamProps = { const upstreamProps = {
...@@ -27,21 +32,29 @@ describe('Linked pipeline', () => { ...@@ -27,21 +32,29 @@ describe('Linked pipeline', () => {
}; };
const findButton = () => wrapper.find(GlButton); const findButton = () => wrapper.find(GlButton);
const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]'); const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title');
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]'); const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label');
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' }); const findLinkedPipeline = () => wrapper.findByTestId('linkedPipeline');
const findLinkedPipelineBody = () => wrapper.findByTestId('linkedPipelineBody');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findPipelineLink = () => wrapper.find('[data-testid="pipelineLink"]'); const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]'); const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button');
const findPopover = () => wrapper.find(GlPopover);
const createWrapper = (propsData, data = []) => { const createWrapper = (propsData, data = []) => {
wrapper = mount(LinkedPipelineComponent, { wrapper = mountExtended(LinkedPipelineComponent, {
propsData, propsData: {
...defaultProps,
...propsData,
},
data() { data() {
return { return {
...data, ...data,
}; };
}, },
provide: {
multiProjectHelpPath: '/ci/pipelines/multi-project-pipelines',
},
}); });
}; };
...@@ -92,7 +105,7 @@ describe('Linked pipeline', () => { ...@@ -92,7 +105,7 @@ describe('Linked pipeline', () => {
}); });
it('should render the tooltip text as the title attribute', () => { it('should render the tooltip text as the title attribute', () => {
const titleAttr = findLinkedPipeline().attributes('title'); const titleAttr = findLinkedPipelineBody().attributes('title');
expect(titleAttr).toContain(mockPipeline.project.name); expect(titleAttr).toContain(mockPipeline.project.name);
expect(titleAttr).toContain(mockPipeline.status.label); expect(titleAttr).toContain(mockPipeline.status.label);
...@@ -168,10 +181,6 @@ describe('Linked pipeline', () => { ...@@ -168,10 +181,6 @@ describe('Linked pipeline', () => {
describe('when isLoading is true', () => { describe('when isLoading is true', () => {
const props = { const props = {
pipeline: mockPipeline,
columnTitle: 'Downstream',
type: DOWNSTREAM,
expanded: false,
isLoading: true, isLoading: true,
}; };
...@@ -184,19 +193,43 @@ describe('Linked pipeline', () => { ...@@ -184,19 +193,43 @@ describe('Linked pipeline', () => {
}); });
}); });
describe('on click/hover', () => { describe('when the user does not have access to the multi-project pipeline viz feature', () => {
const props = {
pipeline: mockPipeline,
columnTitle: 'Downstream',
type: DOWNSTREAM,
expanded: false,
isLoading: false,
};
beforeEach(() => { beforeEach(() => {
const props = { isMultiProjectVizAvailable: false };
createWrapper(props); createWrapper(props);
}); });
it('the multi-project expand button is disabled', () => {
expect(findExpandButton().props('disabled')).toBe(true);
});
it('it adds the popover text inside the DOM', () => {
expect(findPopover().exists()).toBe(true);
expect(findPopover().text()).toContain(
'Gitlab Premium users have access to the multi-project pipeline graph to improve the visualization of these pipelines.',
);
});
});
describe('when the user has access to the multi-project pipeline viz feature', () => {
beforeEach(() => {
createWrapper();
});
it('the multi-project expand button is enabled', () => {
expect(findExpandButton().props('disabled')).toBe(false);
});
it('does not add the popover text inside the DOM', () => {
expect(findPopover().exists()).toBe(false);
});
});
describe('on click/hover', () => {
beforeEach(() => {
createWrapper();
});
it('emits `pipelineClicked` event', () => { it('emits `pipelineClicked` event', () => {
jest.spyOn(wrapper.vm, '$emit'); jest.spyOn(wrapper.vm, '$emit');
findButton().trigger('click'); findButton().trigger('click');
......
...@@ -26,6 +26,7 @@ const processedPipeline = pipelineWithUpstreamDownstream(mockPipelineResponse); ...@@ -26,6 +26,7 @@ const processedPipeline = pipelineWithUpstreamDownstream(mockPipelineResponse);
describe('Linked Pipelines Column', () => { describe('Linked Pipelines Column', () => {
const defaultProps = { const defaultProps = {
columnTitle: 'Downstream', columnTitle: 'Downstream',
isMultiProjectVizAvailable: true,
linkedPipelines: processedPipeline.downstream, linkedPipelines: processedPipeline.downstream,
showLinks: false, showLinks: false,
type: DOWNSTREAM, type: DOWNSTREAM,
...@@ -51,6 +52,9 @@ describe('Linked Pipelines Column', () => { ...@@ -51,6 +52,9 @@ describe('Linked Pipelines Column', () => {
...defaultProps, ...defaultProps,
...props, ...props,
}, },
provide: {
multiProjectHelpPath: 'ci/pipelines/multi-project-pipeline',
},
}); });
}; };
...@@ -67,7 +71,6 @@ describe('Linked Pipelines Column', () => { ...@@ -67,7 +71,6 @@ describe('Linked Pipelines Column', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('it renders correctly', () => { describe('it renders correctly', () => {
......
...@@ -13,6 +13,15 @@ export const mockPipelineResponse = { ...@@ -13,6 +13,15 @@ export const mockPipelineResponse = {
usesNeeds: true, usesNeeds: true,
downstream: null, downstream: null,
upstream: null, upstream: null,
user: {
__typename: 'UserCore',
id: 'gid://gitlab/User/1',
namespace: {
__typename: 'Namespace',
id: 'gid://gitlab/Namespaces::UserNamespace/1',
crossProjectPipelineAvailable: true,
},
},
userPermissions: { userPermissions: {
__typename: 'PipelinePermissions', __typename: 'PipelinePermissions',
updatePipeline: true, updatePipeline: true,
...@@ -780,6 +789,15 @@ export const wrappedPipelineReturn = { ...@@ -780,6 +789,15 @@ export const wrappedPipelineReturn = {
id: 'gid://gitlab/Ci::Pipeline/175', id: 'gid://gitlab/Ci::Pipeline/175',
iid: '38', iid: '38',
complete: true, complete: true,
user: {
__typename: 'UserCore',
id: 'gid://gitlab/User/1',
namespace: {
__typename: 'Namespace',
id: 'gid://gitlab/Namespaces::UserNamespace/1',
crossProjectPipelineAvailable: true,
},
},
usesNeeds: true, usesNeeds: true,
userPermissions: { userPermissions: {
__typename: 'PipelinePermissions', __typename: 'PipelinePermissions',
......
import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlIcon } from '@gitlab/ui';
import TierBadge from '~/vue_shared/components/tier_badge.vue';
describe('Tier badge component', () => {
let wrapper;
const createComponent = (props) =>
shallowMount(TierBadge, {
propsData: {
...props,
},
});
const findBadge = () => wrapper.findComponent(GlBadge);
const findTierText = () => findBadge().text();
const findIcon = () => wrapper.findComponent(GlIcon);
afterEach(() => {
wrapper.destroy();
});
describe('tiers name', () => {
it.each`
tier | tierText
${'free'} | ${'Free'}
${'premium'} | ${'Premium'}
${'ultimate'} | ${'Ultimate'}
`(
'shows $tierText text in the badge and the license icon when $tier prop is passed',
({ tier, tierText }) => {
wrapper = createComponent({ tier });
expect(findTierText()).toBe(tierText);
expect(findIcon().exists()).toBe(true);
expect(findIcon().props().name).toBe('license');
},
);
});
describe('badge size', () => {
const newSize = 'lg';
beforeEach(() => {
wrapper = createComponent({ tier: 'free', size: newSize });
});
it('passes down the size prop to the GlBadge component', () => {
expect(findBadge().props().size).toBe(newSize);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::PipelineHelper do
describe '#js_pipeline_details_data' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
subject(:pipeline_details_data) { helper.js_pipeline_details_data(project, pipeline) }
it 'returns pipeline details data' do
expect(pipeline_details_data).to eq({
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),
multi_project_help_path: help_page_path('ci/pipelines/multi_project_pipelines.md', anchor: 'multi-project-pipeline-visualization'),
pipeline_iid: pipeline.iid,
pipeline_project_path: project.full_path
})
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