Commit 0d86d7e2 authored by Miguel Rincon's avatar Miguel Rincon

Merge branch 'cleanup-pipelines-empty-state-experiment' into 'master'

Convert CI/CD templates picker experiment into default state for empty pipelines page [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!64200
parents 8c59237a f1b867e8
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils'; import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
...@@ -41,6 +42,7 @@ export default { ...@@ -41,6 +42,7 @@ export default {
}, },
data() { data() {
return { return {
starterTemplateName: STARTER_TEMPLATE_NAME,
ciConfigData: {}, ciConfigData: {},
failureType: null, failureType: null,
failureReasons: [], failureReasons: [],
...@@ -160,7 +162,7 @@ export default { ...@@ -160,7 +162,7 @@ export default {
variables() { variables() {
return { return {
projectPath: this.projectFullPath, projectPath: this.projectFullPath,
templateName: STARTER_TEMPLATE_NAME, templateName: this.starterTemplateName,
}; };
}, },
skip({ isNewCiConfigFile }) { skip({ isNewCiConfigFile }) {
...@@ -203,6 +205,9 @@ export default { ...@@ -203,6 +205,9 @@ export default {
} }
}, },
}, },
mounted() {
this.loadTemplateFromURL();
},
methods: { methods: {
hideFailure() { hideFailure() {
this.showFailure = false; this.showFailure = false;
...@@ -258,6 +263,14 @@ export default { ...@@ -258,6 +263,14 @@ export default {
// if the user has made changes to the file that are unsaved. // if the user has made changes to the file that are unsaved.
this.lastCommittedContent = this.currentCiFileContent; this.lastCommittedContent = this.currentCiFileContent;
}, },
loadTemplateFromURL() {
const templateName = queryToObject(window.location.search)?.template;
if (templateName) {
this.starterTemplateName = templateName;
this.setNewEmptyCiConfigFile();
}
},
}, },
}; };
</script> </script>
......
...@@ -16,7 +16,6 @@ export default { ...@@ -16,7 +16,6 @@ export default {
consuming tasks, so you can spend more time creating.`), consuming tasks, so you can spend more time creating.`),
aboutRunnersBtnText: s__('Pipelines|Learn about Runners'), aboutRunnersBtnText: s__('Pipelines|Learn about Runners'),
installRunnersBtnText: s__('Pipelines|Install GitLab Runners'), installRunnersBtnText: s__('Pipelines|Install GitLab Runners'),
getStartedBtnText: s__('Pipelines|Get started with CI/CD'),
codeQualityTitle: s__('Pipelines|Improve code quality with GitLab CI/CD'), codeQualityTitle: s__('Pipelines|Improve code quality with GitLab CI/CD'),
codeQualityDescription: s__(`Pipelines|To keep your codebase simple, codeQualityDescription: s__(`Pipelines|To keep your codebase simple,
readable, and accessible to contributors, use GitLab CI/CD readable, and accessible to contributors, use GitLab CI/CD
...@@ -55,9 +54,6 @@ export default { ...@@ -55,9 +54,6 @@ export default {
ciHelpPagePath() { ciHelpPagePath() {
return helpPagePath('ci/quick_start/index.md'); return helpPagePath('ci/quick_start/index.md');
}, },
isPipelineEmptyStateTemplatesExperimentActive() {
return this.canSetCi && Boolean(getExperimentData('pipeline_empty_state_templates'));
},
isCodeQualityExperimentActive() { isCodeQualityExperimentActive() {
return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough')); return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough'));
}, },
...@@ -81,37 +77,8 @@ export default { ...@@ -81,37 +77,8 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<gitlab-experiment <gitlab-experiment v-if="isCodeQualityExperimentActive" name="code_quality_walkthrough">
v-if="isPipelineEmptyStateTemplatesExperimentActive" <template #control><pipelines-ci-templates /></template>
name="pipeline_empty_state_templates"
>
<template #control>
<gl-empty-state
:title="$options.i18n.title"
:svg-path="emptyStateSvgPath"
:description="$options.i18n.description"
:primary-button-text="$options.i18n.getStartedBtnText"
:primary-button-link="ciHelpPagePath"
/>
</template>
<template #candidate>
<pipelines-ci-templates />
</template>
</gitlab-experiment>
<gitlab-experiment v-else-if="isCodeQualityExperimentActive" name="code_quality_walkthrough">
<template #control>
<gl-empty-state
:title="$options.i18n.title"
:svg-path="emptyStateSvgPath"
:description="$options.i18n.description"
>
<template #actions>
<gl-button :href="ciHelpPagePath" variant="confirm" @click="trackClick()">
{{ $options.i18n.getStartedBtnText }}
</gl-button>
</template>
</gl-empty-state>
</template>
<template #candidate> <template #candidate>
<gl-empty-state <gl-empty-state
:title="$options.i18n.codeQualityTitle" :title="$options.i18n.codeQualityTitle"
...@@ -127,23 +94,7 @@ export default { ...@@ -127,23 +94,7 @@ export default {
</template> </template>
</gitlab-experiment> </gitlab-experiment>
<gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates"> <gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates">
<template #control> <template #control><pipelines-ci-templates /></template>
<gl-empty-state
:title="$options.i18n.title"
:svg-path="emptyStateSvgPath"
:description="$options.i18n.description"
>
<template #actions>
<gl-button
:href="ciHelpPagePath"
variant="confirm"
@click="trackCiRunnerTemplatesClick('get_started_button_clicked')"
>
{{ $options.i18n.getStartedBtnText }}
</gl-button>
</template>
</gl-empty-state>
</template>
<template #candidate> <template #candidate>
<gl-empty-state <gl-empty-state
:title="$options.i18n.title" :title="$options.i18n.title"
...@@ -169,14 +120,7 @@ export default { ...@@ -169,14 +120,7 @@ export default {
</gl-empty-state> </gl-empty-state>
</template> </template>
</gitlab-experiment> </gitlab-experiment>
<gl-empty-state <pipelines-ci-templates v-else-if="canSetCi" />
v-else-if="canSetCi"
:title="$options.i18n.title"
:svg-path="emptyStateSvgPath"
:description="$options.i18n.description"
:primary-button-text="$options.i18n.getStartedBtnText"
:primary-button-link="ciHelpPagePath"
/>
<gl-empty-state <gl-empty-state
v-else v-else
title="" title=""
......
<script> <script>
import { GlAvatar, GlButton, GlCard, GlSprintf } from '@gitlab/ui'; import { GlAvatar, GlButton, GlCard, GlSprintf } from '@gitlab/ui';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { mergeUrlParams } from '~/lib/utils/url_utility'; import { mergeUrlParams } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { HELLO_WORLD_TEMPLATE_KEY } from '../../constants'; import { STARTER_TEMPLATE_NAME } from '~/pipeline_editor/constants';
import Tracking from '~/tracking';
export default { export default {
components: { components: {
...@@ -12,7 +12,8 @@ export default { ...@@ -12,7 +12,8 @@ export default {
GlCard, GlCard,
GlSprintf, GlSprintf,
}, },
HELLO_WORLD_TEMPLATE_KEY, mixins: [Tracking.mixin()],
STARTER_TEMPLATE_NAME,
i18n: { i18n: {
cta: s__('Pipelines|Use template'), cta: s__('Pipelines|Use template'),
testTemplates: { testTemplates: {
...@@ -20,10 +21,10 @@ export default { ...@@ -20,10 +21,10 @@ export default {
subtitle: s__( subtitle: s__(
'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.', 'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
), ),
helloWorld: { gettingStarted: {
title: s__('Pipelines|“Hello world” with GitLab CI/CD'), title: s__('Pipelines|Get started with GitLab CI/CD'),
description: s__( description: s__(
'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a simple pipeline that runs a “Hello world” script.', 'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline.',
), ),
}, },
}, },
...@@ -35,31 +36,30 @@ export default { ...@@ -35,31 +36,30 @@ export default {
description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'), description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
}, },
}, },
inject: ['addCiYmlPath', 'suggestedCiTemplates'], inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
data() { data() {
const templates = this.suggestedCiTemplates.map(({ name, logo }) => { const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
return { return {
name, name,
logo, logo,
link: mergeUrlParams({ template: name }, this.addCiYmlPath), link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
description: sprintf(this.$options.i18n.templates.description, { name }), description: sprintf(this.$options.i18n.templates.description, { name }),
}; };
}); });
return { return {
templates, templates,
helloWorldTemplateUrl: mergeUrlParams( gettingStartedTemplateUrl: mergeUrlParams(
{ template: HELLO_WORLD_TEMPLATE_KEY }, { template: STARTER_TEMPLATE_NAME },
this.addCiYmlPath, this.pipelineEditorPath,
), ),
}; };
}, },
methods: { methods: {
trackEvent(template) { trackEvent(template) {
const tracking = new ExperimentTracking('pipeline_empty_state_templates', { this.track('template_clicked', {
label: template, label: template,
}); });
tracking.event('template_clicked');
}, },
}, },
}; };
...@@ -82,18 +82,18 @@ export default { ...@@ -82,18 +82,18 @@ export default {
<div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div> <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
<div class="gl-mb-3"> <div class="gl-mb-3">
<strong class="gl-text-gray-800 gl-mb-2">{{ <strong class="gl-text-gray-800 gl-mb-2">{{
$options.i18n.testTemplates.helloWorld.title $options.i18n.testTemplates.gettingStarted.title
}}</strong> }}</strong>
</div> </div>
<p class="gl-font-sm">{{ $options.i18n.testTemplates.helloWorld.description }}</p> <p class="gl-font-sm">{{ $options.i18n.testTemplates.gettingStarted.description }}</p>
</div> </div>
<gl-button <gl-button
category="primary" category="primary"
variant="confirm" variant="confirm"
:href="helloWorldTemplateUrl" :href="gettingStartedTemplateUrl"
data-testid="test-template-link" data-testid="test-template-link"
@click="trackEvent($options.HELLO_WORLD_TEMPLATE_KEY)" @click="trackEvent($options.STARTER_TEMPLATE_NAME)"
> >
{{ $options.i18n.cta }} {{ $options.i18n.cta }}
</gl-button> </gl-button>
......
...@@ -35,6 +35,3 @@ export const POST_FAILURE = 'post_failure'; ...@@ -35,6 +35,3 @@ export const POST_FAILURE = 'post_failure';
export const UNSUPPORTED_DATA = 'unsupported_data'; export const UNSUPPORTED_DATA = 'unsupported_data';
export const CHILD_VIEW = 'child'; export const CHILD_VIEW = 'child';
// The key of the template is the same as the filename
export const HELLO_WORLD_TEMPLATE_KEY = 'Hello-World';
...@@ -29,7 +29,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { ...@@ -29,7 +29,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
errorStateSvgPath, errorStateSvgPath,
noPipelinesSvgPath, noPipelinesSvgPath,
newPipelinePath, newPipelinePath,
addCiYmlPath, pipelineEditorPath,
suggestedCiTemplates, suggestedCiTemplates,
canCreatePipeline, canCreatePipeline,
hasGitlabCi, hasGitlabCi,
...@@ -44,7 +44,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { ...@@ -44,7 +44,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
return new Vue({ return new Vue({
el, el,
provide: { provide: {
addCiYmlPath, pipelineEditorPath,
artifactsEndpoint, artifactsEndpoint,
artifactsEndpointPlaceholder, artifactsEndpointPlaceholder,
suggestedCiTemplates: JSON.parse(suggestedCiTemplates), suggestedCiTemplates: JSON.parse(suggestedCiTemplates),
......
...@@ -49,7 +49,6 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -49,7 +49,6 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
enable_pipeline_empty_state_templates_experiment
enable_code_quality_walkthrough_experiment enable_code_quality_walkthrough_experiment
enable_ci_runner_templates_experiment enable_ci_runner_templates_experiment
end end
...@@ -301,18 +300,6 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -301,18 +300,6 @@ class Projects::PipelinesController < Projects::ApplicationController
params.permit(:scope, :username, :ref, :status) params.permit(:scope, :username, :ref, :status)
end end
def enable_pipeline_empty_state_templates_experiment
experiment(:pipeline_empty_state_templates, namespace: project.root_ancestor) do |e|
e.exclude! unless current_user
e.exclude! if @pipelines_count.to_i > 0
e.exclude! if helpers.has_gitlab_ci?(project)
e.control {}
e.candidate {}
e.record!
end
end
def enable_code_quality_walkthrough_experiment def enable_code_quality_walkthrough_experiment
experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e| experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e|
e.exclude! unless current_user e.exclude! unless current_user
......
...@@ -30,9 +30,7 @@ module Ci ...@@ -30,9 +30,7 @@ module Ci
project.has_ci? && project.builds_enabled? project.has_ci? && project.builds_enabled?
end end
# This list of templates is for the pipeline_empty_state_templates experiment def suggested_ci_templates
# and will be cleaned up with https://gitlab.com/gitlab-org/gitlab/-/issues/326299
def experiment_suggested_ci_templates
[ [
{ name: 'Android', logo: image_path('illustrations/third-party-logos/ci_cd-template-logos/android.svg') }, { name: 'Android', logo: image_path('illustrations/third-party-logos/ci_cd-template-logos/android.svg') },
{ name: 'Bash', logo: image_path('illustrations/third-party-logos/ci_cd-template-logos/bash.svg') }, { name: 'Bash', logo: image_path('illustrations/third-party-logos/ci_cd-template-logos/bash.svg') },
......
...@@ -135,10 +135,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -135,10 +135,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
ide_edit_path(project, default_branch_or_main, 'README.md') ide_edit_path(project, default_branch_or_main, 'README.md')
end end
def add_ci_yml_path
add_special_file_path(file_name: ci_config_path_or_default)
end
def add_code_quality_ci_yml_path def add_code_quality_ci_yml_path
add_special_file_path( add_special_file_path(
file_name: ci_config_path_or_default, file_name: ci_config_path_or_default,
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
"ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project), "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project),
"reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project), "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project),
"has-gitlab-ci" => has_gitlab_ci?(@project).to_s, "has-gitlab-ci" => has_gitlab_ci?(@project).to_s,
"add-ci-yml-path" => can?(current_user, :create_pipeline, @project) && @project.present(current_user: current_user).add_ci_yml_path, "pipeline-editor-path" => can?(current_user, :create_pipeline, @project) && project_ci_pipeline_editor_path(@project),
"suggested-ci-templates" => experiment_suggested_ci_templates.to_json, "suggested-ci-templates" => suggested_ci_templates.to_json,
"code-quality-page-path" => @project.present(current_user: current_user).add_code_quality_ci_yml_path, "code-quality-page-path" => @project.present(current_user: current_user).add_code_quality_ci_yml_path,
"ci-runner-settings-path" => project_settings_ci_cd_path(@project, ci_runner_templates: true, anchor: 'js-runners-settings') } } "ci-runner-settings-path" => project_settings_ci_cd_path(@project, ci_runner_templates: true, anchor: 'js-runners-settings') } }
---
name: pipeline_empty_state_templates
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57286
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326299
milestone: '13.11'
type: experiment
group: group::activation
default_enabled: false
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
#
# This file is a template demonstrating the `script` keyword.
# Learn more about this keyword here: https://docs.gitlab.com/ee/ci/yaml/README.html#script
# After committing this template, visit CI/CD > Jobs to see the script output.
job:
script:
# provide a shell script as argument for this keyword.
- echo "Hello World"
...@@ -23924,10 +23924,10 @@ msgstr "" ...@@ -23924,10 +23924,10 @@ msgstr ""
msgid "Pipelines|Editor" msgid "Pipelines|Editor"
msgstr "" msgstr ""
msgid "Pipelines|Get familiar with GitLab CI/CD syntax by starting with a simple pipeline that runs a “Hello world” script." msgid "Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline."
msgstr "" msgstr ""
msgid "Pipelines|Get started with CI/CD" msgid "Pipelines|Get started with GitLab CI/CD"
msgstr "" msgstr ""
msgid "Pipelines|GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time consuming tasks, so you can spend more time creating." msgid "Pipelines|GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time consuming tasks, so you can spend more time creating."
...@@ -24068,9 +24068,6 @@ msgstr "" ...@@ -24068,9 +24068,6 @@ msgstr ""
msgid "Pipelines|parent" msgid "Pipelines|parent"
msgstr "" msgstr ""
msgid "Pipelines|“Hello world” with GitLab CI/CD"
msgstr ""
msgid "Pipeline|Actions" msgid "Pipeline|Actions"
msgstr "" msgstr ""
......
...@@ -284,10 +284,6 @@ RSpec.describe Projects::PipelinesController do ...@@ -284,10 +284,6 @@ RSpec.describe Projects::PipelinesController do
subject { project.namespace } subject { project.namespace }
context 'pipeline_empty_state_templates experiment' do
it_behaves_like 'tracks assignment and records the subject', :pipeline_empty_state_templates, :namespace
end
context 'code_quality_walkthrough experiment' do context 'code_quality_walkthrough experiment' do
it_behaves_like 'tracks assignment and records the subject', :code_quality_walkthrough, :namespace it_behaves_like 'tracks assignment and records the subject', :code_quality_walkthrough, :namespace
end end
......
...@@ -783,7 +783,7 @@ RSpec.describe 'Pipelines', :js do ...@@ -783,7 +783,7 @@ RSpec.describe 'Pipelines', :js do
end end
it 'renders empty state' do it 'renders empty state' do
expect(page).to have_content 'Build with confidence' expect(page).to have_content 'Use a sample CI/CD template'
end end
end end
end end
......
...@@ -12,6 +12,7 @@ import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_edi ...@@ -12,6 +12,7 @@ import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_edi
import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants'; import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql'; import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql'; import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue'; import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue'; import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import { import {
...@@ -47,6 +48,7 @@ describe('Pipeline editor app component', () => { ...@@ -47,6 +48,7 @@ describe('Pipeline editor app component', () => {
let mockApollo; let mockApollo;
let mockBlobContentData; let mockBlobContentData;
let mockCiConfigData; let mockCiConfigData;
let mockGetTemplate;
const createComponent = ({ blobLoading = false, options = {}, provide = {} } = {}) => { const createComponent = ({ blobLoading = false, options = {}, provide = {} } = {}) => {
wrapper = shallowMount(PipelineEditorApp, { wrapper = shallowMount(PipelineEditorApp, {
...@@ -81,6 +83,7 @@ describe('Pipeline editor app component', () => { ...@@ -81,6 +83,7 @@ describe('Pipeline editor app component', () => {
const handlers = [ const handlers = [
[getBlobContent, mockBlobContentData], [getBlobContent, mockBlobContentData],
[getCiConfigData, mockCiConfigData], [getCiConfigData, mockCiConfigData],
[getTemplate, mockGetTemplate],
]; ];
mockApollo = createMockApollo(handlers); mockApollo = createMockApollo(handlers);
...@@ -112,6 +115,7 @@ describe('Pipeline editor app component', () => { ...@@ -112,6 +115,7 @@ describe('Pipeline editor app component', () => {
beforeEach(() => { beforeEach(() => {
mockBlobContentData = jest.fn(); mockBlobContentData = jest.fn();
mockCiConfigData = jest.fn(); mockCiConfigData = jest.fn();
mockGetTemplate = jest.fn();
}); });
afterEach(() => { afterEach(() => {
...@@ -318,4 +322,29 @@ describe('Pipeline editor app component', () => { ...@@ -318,4 +322,29 @@ describe('Pipeline editor app component', () => {
expect(findEditorHome().exists()).toBe(true); expect(findEditorHome().exists()).toBe(true);
}); });
}); });
describe('when a template parameter is present in the URL', () => {
const { location } = window;
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost?template=Android');
});
afterEach(() => {
window.location = location;
});
it('renders the given template', async () => {
await createComponentWithApollo();
expect(mockGetTemplate).toHaveBeenCalledWith({
projectPath: mockProjectFullPath,
templateName: 'Android',
});
expect(findEmptyState().exists()).toBe(false);
expect(findTextEditor().exists()).toBe(true);
});
});
}); });
import '~/commons';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue'; import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
describe('Pipelines Empty State', () => { describe('Pipelines Empty State', () => {
let wrapper; let wrapper;
const findIllustration = () => wrapper.find('img'); const findIllustration = () => wrapper.find('img');
const findButton = () => wrapper.find('a'); const findButton = () => wrapper.find('a');
const pipelinesCiTemplates = () => wrapper.findComponent(PipelinesCiTemplates);
const createWrapper = (props = {}) => { const createWrapper = (props = {}) => {
wrapper = mount(EmptyState, { wrapper = mount(EmptyState, {
provide: {
pipelineEditorPath: '',
suggestedCiTemplates: [],
},
propsData: { propsData: {
emptyStateSvgPath: 'foo.svg', emptyStateSvgPath: 'foo.svg',
canSetCi: true, canSetCi: true,
...@@ -27,27 +34,8 @@ describe('Pipelines Empty State', () => { ...@@ -27,27 +34,8 @@ describe('Pipelines Empty State', () => {
wrapper = null; wrapper = null;
}); });
it('should render empty state SVG', () => { it('should render the CI/CD templates', () => {
expect(findIllustration().attributes('src')).toBe('foo.svg'); expect(pipelinesCiTemplates()).toExist();
});
it('should render empty state header', () => {
expect(wrapper.text()).toContain('Build with confidence');
});
it('should render empty state information', () => {
expect(wrapper.text()).toContain(
'GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time',
'consuming tasks, so you can spend more time creating',
);
});
it('should render button with help path', () => {
expect(findButton().attributes('href')).toBe('/help/ci/quick_start/index.md');
});
it('should render button text', () => {
expect(findButton().text()).toBe('Get started with CI/CD');
}); });
}); });
......
import '~/commons';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ExperimentTracking from '~/experimentation/experiment_tracking'; import { mockTracking } from 'helpers/tracking_helper';
import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue'; import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
const addCiYmlPath = "/-/new/main?commit_message='Add%20.gitlab-ci.yml'"; const pipelineEditorPath = '/-/ci/editor';
const suggestedCiTemplates = [ const suggestedCiTemplates = [
{ name: 'Android', logo: '/assets/illustrations/logos/android.svg' }, { name: 'Android', logo: '/assets/illustrations/logos/android.svg' },
{ name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' }, { name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' },
{ name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' }, { name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
]; ];
jest.mock('~/experimentation/experiment_tracking');
describe('Pipelines CI Templates', () => { describe('Pipelines CI Templates', () => {
let wrapper; let wrapper;
let trackingSpy;
const GlEmoji = { template: '<img/>' };
const createWrapper = () => { const createWrapper = () => {
return shallowMount(PipelinesCiTemplate, { return shallowMount(PipelinesCiTemplate, {
provide: { provide: {
addCiYmlPath, pipelineEditorPath,
suggestedCiTemplates, suggestedCiTemplates,
}, },
stubs: {
GlEmoji,
},
}); });
}; };
...@@ -44,9 +39,9 @@ describe('Pipelines CI Templates', () => { ...@@ -44,9 +39,9 @@ describe('Pipelines CI Templates', () => {
wrapper = createWrapper(); wrapper = createWrapper();
}); });
it('links to the hello world template', () => { it('links to the getting started template', () => {
expect(findTestTemplateLinks().at(0).attributes('href')).toBe( expect(findTestTemplateLinks().at(0).attributes('href')).toBe(
addCiYmlPath.concat('&template=Hello-World'), pipelineEditorPath.concat('?template=Getting-Started'),
); );
}); });
}); });
...@@ -68,7 +63,7 @@ describe('Pipelines CI Templates', () => { ...@@ -68,7 +63,7 @@ describe('Pipelines CI Templates', () => {
it('links to the correct template', () => { it('links to the correct template', () => {
expect(findTemplateLinks().at(0).attributes('href')).toBe( expect(findTemplateLinks().at(0).attributes('href')).toBe(
addCiYmlPath.concat('&template=Android'), pipelineEditorPath.concat('?template=Android'),
); );
}); });
...@@ -88,24 +83,25 @@ describe('Pipelines CI Templates', () => { ...@@ -88,24 +83,25 @@ describe('Pipelines CI Templates', () => {
describe('tracking', () => { describe('tracking', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper(); wrapper = createWrapper();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
it('sends an event when template is clicked', () => { it('sends an event when template is clicked', () => {
findTemplateLinks().at(0).vm.$emit('click'); findTemplateLinks().at(0).vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith('pipeline_empty_state_templates', { expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
label: 'Android', label: 'Android',
}); });
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('template_clicked');
}); });
it('sends an event when Hello-World template is clicked', () => { it('sends an event when Getting-Started template is clicked', () => {
findTestTemplateLinks().at(0).vm.$emit('click'); findTestTemplateLinks().at(0).vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith('pipeline_empty_state_templates', { expect(trackingSpy).toHaveBeenCalledTimes(1);
label: 'Hello-World', expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
label: 'Getting-Started',
}); });
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('template_clicked');
}); });
}); });
}); });
...@@ -12,6 +12,7 @@ import createFlash from '~/flash'; ...@@ -12,6 +12,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue'; import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue'; import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { RAW_TEXT_WARNING } from '~/pipelines/constants'; import { RAW_TEXT_WARNING } from '~/pipelines/constants';
import Store from '~/pipelines/stores/pipelines_store'; import Store from '~/pipelines/stores/pipelines_store';
...@@ -82,6 +83,10 @@ describe('Pipelines', () => { ...@@ -82,6 +83,10 @@ describe('Pipelines', () => {
const createComponent = (props = defaultProps) => { const createComponent = (props = defaultProps) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(PipelinesComponent, { mount(PipelinesComponent, {
provide: {
pipelineEditorPath: '',
suggestedCiTemplates: [],
},
propsData: { propsData: {
store: new Store(), store: new Store(),
projectId: mockProjectId, projectId: mockProjectId,
...@@ -551,21 +556,27 @@ describe('Pipelines', () => { ...@@ -551,21 +556,27 @@ describe('Pipelines', () => {
await waitForPromises(); await waitForPromises();
}); });
it('renders empty state', () => { it('renders the CI/CD templates', () => {
expect(findEmptyState().text()).toContain('Build with confidence'); expect(wrapper.find(PipelinesCiTemplates)).toExist();
expect(findEmptyState().text()).toContain(
'GitLab CI/CD can automatically build, test, and deploy your code.',
);
expect(findEmptyState().find(GlButton).text()).toBe('Get started with CI/CD');
expect(findEmptyState().find(GlButton).attributes('href')).toBe(
'/help/ci/quick_start/index.md',
);
}); });
describe('when the code_quality_walkthrough experiment is active', () => { describe('when the code_quality_walkthrough experiment is active', () => {
beforeAll(() => { beforeAll(() => {
getExperimentData.mockImplementation((name) => name === 'code_quality_walkthrough'); getExperimentData.mockImplementation((name) => name === 'code_quality_walkthrough');
});
describe('the control state', () => {
beforeAll(() => {
getExperimentVariant.mockReturnValue('control');
});
it('renders the CI/CD templates', () => {
expect(wrapper.find(PipelinesCiTemplates)).toExist();
});
});
describe('the candidate state', () => {
beforeAll(() => {
getExperimentVariant.mockReturnValue('candidate'); getExperimentVariant.mockReturnValue('candidate');
}); });
...@@ -576,10 +587,25 @@ describe('Pipelines', () => { ...@@ -576,10 +587,25 @@ describe('Pipelines', () => {
); );
}); });
}); });
});
describe('when the ci_runner_templates experiment is active', () => { describe('when the ci_runner_templates experiment is active', () => {
beforeAll(() => { beforeAll(() => {
getExperimentData.mockImplementation((name) => name === 'ci_runner_templates'); getExperimentData.mockImplementation((name) => name === 'ci_runner_templates');
});
describe('the control state', () => {
beforeAll(() => {
getExperimentVariant.mockReturnValue('control');
});
it('renders the CI/CD templates', () => {
expect(wrapper.find(PipelinesCiTemplates)).toExist();
});
});
describe('the candidate state', () => {
beforeAll(() => {
getExperimentVariant.mockReturnValue('candidate'); getExperimentVariant.mockReturnValue('candidate');
}); });
...@@ -599,6 +625,7 @@ describe('Pipelines', () => { ...@@ -599,6 +625,7 @@ describe('Pipelines', () => {
); );
}); });
}); });
});
it('does not render filtered search', () => { it('does not render filtered search', () => {
expect(findFilteredSearch().exists()).toBe(false); expect(findFilteredSearch().exists()).toBe(false);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment