Commit 8600059f authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'refactor-ci-templates' into 'master'

Refactor pipeline empty state ci templates

See merge request gitlab-org/gitlab!84596
parents fcfee0cc fe2a5493
......@@ -100,7 +100,5 @@ export const I18N = {
subtitle: s__(
"Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD.",
),
description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
cta: s__('Pipelines|Use template'),
},
};
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
import PipelinesCiTemplates from './pipelines_ci_templates.vue';
import PipelinesCiTemplates from './empty_state/pipelines_ci_templates.vue';
export default {
i18n: {
......
<script>
import { GlAvatar, GlButton } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import Tracking from '~/tracking';
export default {
components: {
GlAvatar,
GlButton,
},
mixins: [Tracking.mixin()],
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
data() {
const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
return {
name,
logo,
link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
description: sprintf(this.$options.i18n.description, { name }),
};
});
return {
templates,
};
},
methods: {
trackEvent(template) {
this.track('template_clicked', {
label: template,
});
},
},
i18n: {
description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
cta: s__('Pipelines|Use template'),
},
AVATAR_SHAPE_OPTION_RECT,
};
</script>
<template>
<ul class="gl-list-style-none gl-pl-0">
<li v-for="template in templates" :key="template.name">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
>
<div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
<gl-avatar
:src="template.logo"
:size="48"
class="gl-mr-5 gl-bg-white dark-mode-override"
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
:alt="template.name"
data-testid="template-logo"
/>
<div class="gl-flex-direction-row">
<div class="gl-mb-3">
<strong class="gl-text-gray-800" data-testid="template-name">
{{ template.name }}
</strong>
</div>
<p class="gl-mb-0 gl-font-sm" data-testid="template-description">
{{ template.description }}
</p>
</div>
</div>
<gl-button
category="primary"
variant="confirm"
:href="template.link"
data-testid="template-link"
@click="trackEvent(template.name)"
>
{{ $options.i18n.cta }}
</gl-button>
</div>
</li>
</ul>
</template>
<script>
import { GlAvatar, GlButton, GlCard, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import { GlButton, GlCard, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { sprintf } from '~/locale';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import {
STARTER_TEMPLATE_NAME,
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
......@@ -11,21 +9,22 @@ import {
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
} from '~/pipeline_editor/constants';
import Tracking from '~/tracking';
import { helpPagePath } from '~/helpers/help_page_helper';
import { isExperimentVariant } from '~/experimentation/utils';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { isExperimentVariant } from '~/experimentation/utils';
import Tracking from '~/tracking';
import CiTemplates from './ci_templates.vue';
export default {
components: {
GlAvatar,
GlButton,
GlCard,
GlSprintf,
GlIcon,
GlLink,
GitlabExperiment,
CiTemplates,
},
mixins: [Tracking.mixin()],
STARTER_TEMPLATE_NAME,
......@@ -34,8 +33,7 @@ export default {
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
AVATAR_SHAPE_OPTION_RECT,
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
inject: ['pipelineEditorPath'],
props: {
ciRunnerSettingsPath: {
type: String,
......@@ -49,17 +47,7 @@ export default {
},
},
data() {
const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
return {
name,
logo,
link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
description: sprintf(this.$options.I18N.templates.description, { name }),
};
});
return {
templates,
gettingStartedTemplateUrl: mergeUrlParams(
{ template: STARTER_TEMPLATE_NAME },
this.pipelineEditorPath,
......@@ -179,43 +167,7 @@ export default {
<h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.templates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.I18N.templates.subtitle }}</p>
<ul class="gl-list-style-none gl-pl-0">
<li v-for="template in templates" :key="template.name">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
>
<div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
<gl-avatar
:src="template.logo"
:size="48"
class="gl-mr-5 gl-bg-white dark-mode-override"
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
:alt="template.name"
data-testid="template-logo"
/>
<div class="gl-flex-direction-row">
<div class="gl-mb-3">
<strong class="gl-text-gray-800" data-testid="template-name">
{{ template.name }}
</strong>
</div>
<p class="gl-mb-0 gl-font-sm" data-testid="template-description">
{{ template.description }}
</p>
</div>
</div>
<gl-button
category="primary"
variant="confirm"
:href="template.link"
data-testid="template-link"
@click="trackEvent(template.name)"
>
{{ $options.I18N.templates.cta }}
</gl-button>
</div>
</li>
</ul>
<ci-templates />
</template>
</div>
</template>
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import CiTemplates from '~/pipelines/components/pipelines_list/empty_state/ci_templates.vue';
const pipelineEditorPath = '/-/ci/editor';
const suggestedCiTemplates = [
{ name: 'Android', logo: '/assets/illustrations/logos/android.svg' },
{ name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' },
{ name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
];
describe('CI Templates', () => {
let wrapper;
let trackingSpy;
const createWrapper = () => {
return shallowMountExtended(CiTemplates, {
provide: {
pipelineEditorPath,
suggestedCiTemplates,
},
});
};
const findTemplateDescription = () => wrapper.findByTestId('template-description');
const findTemplateLink = () => wrapper.findByTestId('template-link');
const findTemplateName = () => wrapper.findByTestId('template-name');
const findTemplateLogo = () => wrapper.findByTestId('template-logo');
beforeEach(() => {
wrapper = createWrapper();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('renders template list', () => {
it('renders all suggested templates', () => {
const content = wrapper.text();
expect(content).toContain('Android', 'Bash', 'C++');
});
it('has the correct template name', () => {
expect(findTemplateName().text()).toBe('Android');
});
it('links to the correct template', () => {
expect(findTemplateLink().attributes('href')).toBe(
pipelineEditorPath.concat('?template=Android'),
);
});
it('has the description of the template', () => {
expect(findTemplateDescription().text()).toBe(
'CI/CD template to test and deploy your Android project.',
);
});
it('has the right logo of the template', () => {
expect(findTemplateLogo().attributes('src')).toBe('/assets/illustrations/logos/android.svg');
});
});
describe('tracking', () => {
beforeEach(() => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
});
it('sends an event when template is clicked', () => {
findTemplateLink().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
label: 'Android',
});
});
});
});
import '~/commons';
import { GlButton, GlSprintf } from '@gitlab/ui';
import { sprintf } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { stubExperiments } from 'helpers/experimentation_helper';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
import CiTemplates from '~/pipelines/components/pipelines_list/empty_state/ci_templates.vue';
import {
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
......@@ -16,11 +16,6 @@ import {
} from '~/pipeline_editor/constants';
const pipelineEditorPath = '/-/ci/editor';
const suggestedCiTemplates = [
{ name: 'Android', logo: '/assets/illustrations/logos/android.svg' },
{ name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' },
{ name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
];
jest.mock('~/experimentation/experiment_tracking');
......@@ -29,21 +24,17 @@ describe('Pipelines CI Templates', () => {
let trackingSpy;
const createWrapper = (propsData = {}, stubs = {}) => {
return shallowMountExtended(PipelinesCiTemplate, {
return shallowMountExtended(PipelinesCiTemplates, {
provide: {
pipelineEditorPath,
suggestedCiTemplates,
},
propsData,
stubs,
});
};
const findTestTemplateLinks = () => wrapper.findAll('[data-testid="test-template-link"]');
const findTemplateDescriptions = () => wrapper.findAll('[data-testid="template-description"]');
const findTemplateLinks = () => wrapper.findAll('[data-testid="template-link"]');
const findTemplateNames = () => wrapper.findAll('[data-testid="template-name"]');
const findTemplateLogos = () => wrapper.findAll('[data-testid="template-logo"]');
const findTestTemplateLink = () => wrapper.findByTestId('test-template-link');
const findCiTemplates = () => wrapper.findComponent(CiTemplates);
const findSettingsLink = () => wrapper.findByTestId('settings-link');
const findDocumentationLink = () => wrapper.findByTestId('documentation-link');
const findSettingsButton = () => wrapper.findByTestId('settings-button');
......@@ -59,63 +50,24 @@ describe('Pipelines CI Templates', () => {
});
it('links to the getting started template', () => {
expect(findTestTemplateLinks().at(0).attributes('href')).toBe(
expect(findTestTemplateLink().attributes('href')).toBe(
pipelineEditorPath.concat('?template=Getting-Started'),
);
});
});
describe('renders template list', () => {
beforeEach(() => {
wrapper = createWrapper();
});
it('renders all suggested templates', () => {
const content = wrapper.text();
expect(content).toContain('Android', 'Bash', 'C++');
});
it('has the correct template name', () => {
expect(findTemplateNames().at(0).text()).toBe('Android');
});
it('links to the correct template', () => {
expect(findTemplateLinks().at(0).attributes('href')).toBe(
pipelineEditorPath.concat('?template=Android'),
);
});
it('has the description of the template', () => {
expect(findTemplateDescriptions().at(0).text()).toBe(
sprintf(I18N.templates.description, { name: 'Android' }),
);
});
it('has the right logo of the template', () => {
expect(findTemplateLogos().at(0).attributes('src')).toBe(
'/assets/illustrations/logos/android.svg',
);
});
});
describe('tracking', () => {
beforeEach(() => {
wrapper = createWrapper();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
it('sends an event when template is clicked', () => {
findTemplateLinks().at(0).vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
label: 'Android',
});
afterEach(() => {
unmockTracking();
});
it('sends an event when Getting-Started template is clicked', () => {
findTestTemplateLinks().at(0).vm.$emit('click');
findTestTemplateLink().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
......@@ -198,8 +150,8 @@ describe('Pipelines CI Templates', () => {
});
it(`renders the templates: ${templatesRendered}`, () => {
expect(findTestTemplateLinks().exists()).toBe(templatesRendered);
expect(findTemplateLinks().exists()).toBe(templatesRendered);
expect(findTestTemplateLink().exists()).toBe(templatesRendered);
expect(findCiTemplates().exists()).toBe(templatesRendered);
});
},
);
......
import '~/commons';
import { mount } from '@vue/test-utils';
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
describe('Pipelines Empty State', () => {
let wrapper;
......
......@@ -14,7 +14,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
import Store from '~/pipelines/stores/pipelines_store';
......
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