Commit 8486ee1e authored by Alex Buijs's avatar Alex Buijs

Add runners availability section to the pipeline zeostate page

As an experiment to drive free to paid conversion.
parent dcfdc833
import { s__ } from '~/locale';
// Values for CI_CONFIG_STATUS_* comes from lint graphQL
export const CI_CONFIG_STATUS_INVALID = 'INVALID';
export const CI_CONFIG_STATUS_VALID = 'VALID';
......@@ -62,3 +64,45 @@ export const TEMPLATE_REPOSITORY_URL =
'https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates';
export const COMMIT_SHA_POLL_INTERVAL = 1000;
export const RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME = 'runners_availability_section';
export const RUNNERS_SETTINGS_LINK_CLICKED_EVENT = 'runners_settings_link_clicked';
export const RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT = 'runners_documentation_link_clicked';
export const RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT = 'runners_settings_button_clicked';
export const I18N = {
title: s__('Pipelines|Get started with GitLab CI/CD'),
runners: {
title: s__('Pipelines|Runners are available to run your jobs now'),
subtitle: s__(
'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.',
),
},
noRunners: {
title: s__('Pipelines|No runners detected'),
subtitle: s__(
'Pipelines|A GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. Install GitLab Runner and register your own runners to get started with CI/CD.',
),
cta: s__('Pipelines|Install GitLab Runner'),
},
learnBasics: {
title: s__('Pipelines|Learn the basics of pipelines and .yml files'),
subtitle: s__(
'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
),
gettingStarted: {
title: s__('Pipelines|"Hello world" with GitLab CI'),
description: s__(
'Pipelines|Get familiar with GitLab CI syntax by setting up a simple pipeline running a "Hello world" script to see how it runs, explore how CI/CD works.',
),
cta: s__('Pipelines|Try test template'),
},
},
templates: {
title: s__('Pipelines|Ready to set up CI/CD for your project?'),
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'),
},
};
......@@ -49,6 +49,11 @@ export default {
required: false,
default: null,
},
anyRunnersAvailable: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
ciHelpPagePath() {
......@@ -120,7 +125,11 @@ export default {
</gl-empty-state>
</template>
</gitlab-experiment>
<pipelines-ci-templates v-else-if="canSetCi" />
<pipelines-ci-templates
v-else-if="canSetCi"
:ci-runner-settings-path="ciRunnerSettingsPath"
:any-runners-available="anyRunnersAvailable"
/>
<gl-empty-state
v-else
title=""
......
......@@ -112,6 +112,11 @@ export default {
required: false,
default: null,
},
anyRunnersAvailable: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
......@@ -382,6 +387,7 @@ export default {
:can-set-ci="canCreatePipeline"
:code-quality-page-path="codeQualityPagePath"
:ci-runner-settings-path="ciRunnerSettingsPath"
:any-runners-available="anyRunnersAvailable"
/>
<gl-empty-state
......
<script>
import { GlAvatar, GlButton, GlCard, GlSprintf } from '@gitlab/ui';
import { GlAvatar, GlButton, GlCard, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import { STARTER_TEMPLATE_NAME } from '~/pipeline_editor/constants';
import { sprintf } from '~/locale';
import {
STARTER_TEMPLATE_NAME,
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
} from '~/pipeline_editor/constants';
import { helpPagePath } from '~/helpers/help_page_helper';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { getExperimentData } from '~/experimentation/utils';
import Tracking from '~/tracking';
export default {
......@@ -11,39 +22,37 @@ export default {
GlButton,
GlCard,
GlSprintf,
GlIcon,
GlLink,
GitlabExperiment,
},
mixins: [Tracking.mixin()],
STARTER_TEMPLATE_NAME,
i18n: {
cta: s__('Pipelines|Use template'),
testTemplates: {
title: s__('Pipelines|Use a sample CI/CD template'),
subtitle: s__(
'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
),
gettingStarted: {
title: s__('Pipelines|Get started with GitLab CI/CD'),
description: s__(
'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline.',
),
},
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
props: {
ciRunnerSettingsPath: {
type: String,
required: false,
default: null,
},
templates: {
title: s__('Pipelines|Use a CI/CD template'),
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.'),
anyRunnersAvailable: {
type: Boolean,
required: false,
default: true,
},
},
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.templates.description, { name }),
description: sprintf(this.$options.I18N.templates.description, { name }),
};
});
......@@ -53,39 +62,104 @@ export default {
{ template: STARTER_TEMPLATE_NAME },
this.pipelineEditorPath,
),
tracker: null,
};
},
computed: {
sharedRunnersHelpPagePath() {
return helpPagePath('ci/runners/runners_scope', { anchor: 'shared-runners' });
},
runnersAvailabilitySectionExperimentEnabled() {
return Boolean(getExperimentData(RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME));
},
},
created() {
this.tracker = new ExperimentTracking(RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME);
},
methods: {
trackEvent(template) {
this.track('template_clicked', {
label: template,
});
},
trackExperimentEvent(action) {
this.tracker.event(action);
},
},
};
</script>
<template>
<div>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.testTemplates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">
<gl-sprintf :message="$options.i18n.testTemplates.subtitle">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.I18N.title }}</h2>
<div class="row gl-mb-8">
<div class="col-12">
<gitlab-experiment :name="$options.RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME">
<template #candidate>
<div v-if="anyRunnersAvailable">
<h2 class="gl-font-base gl-text-gray-900">
<gl-icon name="check-circle-filled" class="gl-text-green-500 gl-mr-2" :size="12" />
{{ $options.I18N.runners.title }}
</h2>
<p class="gl-text-gray-800 gl-mb-6">
<gl-sprintf :message="$options.I18N.runners.subtitle">
<template #settingsLink="{ content }">
<gl-link
data-testid="settings-link"
:href="ciRunnerSettingsPath"
@click="trackExperimentEvent($options.RUNNERS_SETTINGS_LINK_CLICKED_EVENT)"
>{{ content }}</gl-link
>
</template>
<template #docsLink="{ content }">
<gl-link
data-testid="documentation-link"
:href="sharedRunnersHelpPagePath"
@click="trackExperimentEvent($options.RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT)"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</p>
</div>
<div v-else>
<h2 class="gl-font-base gl-text-gray-900">
<gl-icon name="warning-solid" class="gl-text-red-600 gl-mr-2" :size="14" />
{{ $options.I18N.noRunners.title }}
</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.I18N.noRunners.subtitle }}</p>
<gl-button
data-testid="settings-button"
category="primary"
variant="confirm"
:href="ciRunnerSettingsPath"
@click="trackExperimentEvent($options.RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT)"
>
{{ $options.I18N.noRunners.cta }}
</gl-button>
</div>
</template>
</gitlab-experiment>
<template v-if="!runnersAvailabilitySectionExperimentEnabled || anyRunnersAvailable">
<h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.learnBasics.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">
<gl-sprintf :message="$options.I18N.learnBasics.subtitle">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<div class="gl-lg-w-25p gl-lg-pr-5 gl-mb-8">
<gl-card>
<div class="gl-flex-direction-row">
<div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
<div class="gl-mb-3">
<strong class="gl-text-gray-800 gl-mb-2">{{
$options.i18n.testTemplates.gettingStarted.title
}}</strong>
<strong class="gl-text-gray-800 gl-mb-2">
{{ $options.I18N.learnBasics.gettingStarted.title }}
</strong>
</div>
<p class="gl-font-sm">{{ $options.i18n.testTemplates.gettingStarted.description }}</p>
<p class="gl-font-sm">{{ $options.I18N.learnBasics.gettingStarted.description }}</p>
</div>
<gl-button
......@@ -95,51 +169,51 @@ export default {
data-testid="test-template-link"
@click="trackEvent($options.STARTER_TEMPLATE_NAME)"
>
{{ $options.i18n.cta }}
{{ $options.I18N.learnBasics.gettingStarted.cta }}
</gl-button>
</gl-card>
</div>
</div>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.templates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.i18n.templates.subtitle }}</p>
<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="64"
class="gl-mr-6 gl-bg-white dark-mode-override"
shape="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>
<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="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>
<p class="gl-mb-0 gl-font-sm" data-testid="template-description">
{{ template.description }}
</p>
</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>
<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>
</li>
</ul>
</template>
</div>
</template>
......@@ -39,6 +39,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
params,
codeQualityPagePath,
ciRunnerSettingsPath,
anyRunnersAvailable,
} = el.dataset;
return new Vue({
......@@ -78,6 +79,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
params: JSON.parse(params),
codeQualityPagePath,
ciRunnerSettingsPath,
anyRunnersAvailable: parseBoolean(anyRunnersAvailable),
},
});
},
......
......@@ -56,6 +56,7 @@ class Projects::PipelinesController < Projects::ApplicationController
format.html do
enable_code_quality_walkthrough_experiment
enable_ci_runner_templates_experiment
enable_runners_availability_section_experiment
end
format.json do
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
......@@ -335,6 +336,18 @@ class Projects::PipelinesController < Projects::ApplicationController
end
end
def enable_runners_availability_section_experiment
return unless current_user
return unless can?(current_user, :create_pipeline, project)
return if @pipelines_count.to_i > 0
return if helpers.has_gitlab_ci?(project)
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
e.candidate {}
e.publish_to_database
end
end
def should_track_ci_cd_pipelines?
params[:chart].blank? || params[:chart] == 'pipelines'
end
......
......@@ -78,6 +78,37 @@ module Ci
pipeline.stuck?
end
def pipelines_list_data(project, list_url)
artifacts_endpoint_placeholder = ':pipeline_artifacts_id'
data = {
endpoint: list_url,
project_id: project.id,
params: params.to_json,
artifacts_endpoint: downloadable_artifacts_project_pipeline_path(project, artifacts_endpoint_placeholder, format: :json),
artifacts_endpoint_placeholder: artifacts_endpoint_placeholder,
pipeline_schedule_url: pipeline_schedules_path(project),
empty_state_svg_path: image_path('illustrations/pipelines_empty.svg'),
error_state_svg_path: image_path('illustrations/pipelines_failed.svg'),
no_pipelines_svg_path: image_path('illustrations/pipelines_pending.svg'),
can_create_pipeline: can?(current_user, :create_pipeline, project).to_s,
new_pipeline_path: can?(current_user, :create_pipeline, project) && new_project_pipeline_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),
has_gitlab_ci: has_gitlab_ci?(project).to_s,
pipeline_editor_path: can?(current_user, :create_pipeline, project) && project_ci_pipeline_editor_path(project),
suggested_ci_templates: suggested_ci_templates.to_json,
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')
}
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
e.candidate { data[:any_runners_available] = project.active_runners.exists?.to_s }
end
data
end
private
def warning_markdown(pipeline)
......
- page_title _('Pipelines')
- add_page_specific_style 'page_bundles/pipelines'
- add_page_specific_style 'page_bundles/ci_status'
- artifacts_endpoint_placeholder = ':pipeline_artifacts_id'
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
- list_url = project_pipelines_path(@project, format: :json, code_quality_walkthrough: params[:code_quality_walkthrough])
- add_page_startup_api_call list_url
#pipelines-list-vue{ data: { endpoint: list_url,
project_id: @project.id,
params: params.to_json,
"artifacts-endpoint" => downloadable_artifacts_project_pipeline_path(@project, artifacts_endpoint_placeholder, format: :json),
"artifacts-endpoint-placeholder" => artifacts_endpoint_placeholder,
"pipeline-schedule-url" => pipeline_schedules_path(@project),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
"no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_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),
"has-gitlab-ci" => has_gitlab_ci?(@project).to_s,
"pipeline-editor-path" => can?(current_user, :create_pipeline, @project) && project_ci_pipeline_editor_path(@project),
"suggested-ci-templates" => suggested_ci_templates.to_json,
"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') } }
#pipelines-list-vue{ data: pipelines_list_data(@project, list_url) }
---
name: runners_availability_section
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80717
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352850
milestone: '14.9'
type: experiment
group: group::activation
default_enabled: false
......@@ -26906,6 +26906,12 @@ msgstr ""
msgid "Pipelines settings for '%{project_name}' were successfully updated."
msgstr ""
msgid "Pipelines|\"Hello world\" with GitLab CI"
msgstr ""
msgid "Pipelines|A GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. Install GitLab Runner and register your own runners to get started with CI/CD."
msgstr ""
msgid "Pipelines|API"
msgstr ""
......@@ -26957,7 +26963,7 @@ msgstr ""
msgid "Pipelines|Editor"
msgstr ""
msgid "Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline."
msgid "Pipelines|Get familiar with GitLab CI syntax by setting up a simple pipeline running a \"Hello world\" script to see how it runs, explore how CI/CD works."
msgstr ""
msgid "Pipelines|Get started with GitLab CI/CD"
......@@ -26966,12 +26972,18 @@ 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."
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."
msgstr ""
msgid "Pipelines|If you are unsure, please ask a project maintainer to review it for you."
msgstr ""
msgid "Pipelines|Improve code quality with GitLab CI/CD"
msgstr ""
msgid "Pipelines|Install GitLab Runner"
msgstr ""
msgid "Pipelines|Install GitLab Runners"
msgstr ""
......@@ -26984,6 +26996,9 @@ msgstr ""
msgid "Pipelines|Learn about Runners"
msgstr ""
msgid "Pipelines|Learn the basics of pipelines and .yml files"
msgstr ""
msgid "Pipelines|Lint"
msgstr ""
......@@ -26999,6 +27014,9 @@ msgstr ""
msgid "Pipelines|More Information"
msgstr ""
msgid "Pipelines|No runners detected"
msgstr ""
msgid "Pipelines|No triggers have been created yet. Add one using the form above."
msgstr ""
......@@ -27011,9 +27029,15 @@ msgstr ""
msgid "Pipelines|Project cache successfully reset."
msgstr ""
msgid "Pipelines|Ready to set up CI/CD for your project?"
msgstr ""
msgid "Pipelines|Revoke trigger"
msgstr ""
msgid "Pipelines|Runners are available to run your jobs now"
msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
......@@ -27077,15 +27101,12 @@ msgstr ""
msgid "Pipelines|Trigger user has insufficient permissions to project"
msgstr ""
msgid "Pipelines|Use a CI/CD template"
msgid "Pipelines|Try test template"
msgstr ""
msgid "Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works."
msgstr ""
msgid "Pipelines|Use a sample CI/CD template"
msgstr ""
msgid "Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD."
msgstr ""
......
......@@ -299,6 +299,10 @@ RSpec.describe Projects::PipelinesController do
context 'ci_runner_templates experiment' do
it_behaves_like 'tracks assignment and records the subject', :ci_runner_templates, :namespace
end
context 'runners_availability_section experiment' do
it_behaves_like 'tracks assignment and records the subject', :runners_availability_section, :namespace
end
end
describe 'GET #show' do
......
......@@ -913,7 +913,7 @@ RSpec.describe 'Pipelines', :js do
end
it 'renders empty state' do
expect(page).to have_content 'Use a sample CI/CD template'
expect(page).to have_content 'Try test template'
end
end
end
......
import '~/commons';
import { shallowMount } from '@vue/test-utils';
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 { 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 {
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
} from '~/pipeline_editor/constants';
const pipelineEditorPath = '/-/ci/editor';
const suggestedCiTemplates = [
......@@ -10,16 +22,20 @@ const suggestedCiTemplates = [
{ name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
];
jest.mock('~/experimentation/experiment_tracking');
describe('Pipelines CI Templates', () => {
let wrapper;
let trackingSpy;
const createWrapper = () => {
return shallowMount(PipelinesCiTemplate, {
const createWrapper = (propsData = {}, stubs = {}) => {
return shallowMountExtended(PipelinesCiTemplate, {
provide: {
pipelineEditorPath,
suggestedCiTemplates,
},
propsData,
stubs,
});
};
......@@ -28,6 +44,9 @@ describe('Pipelines CI Templates', () => {
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 findSettingsLink = () => wrapper.findByTestId('settings-link');
const findDocumentationLink = () => wrapper.findByTestId('documentation-link');
const findSettingsButton = () => wrapper.findByTestId('settings-button');
afterEach(() => {
wrapper.destroy();
......@@ -69,7 +88,7 @@ describe('Pipelines CI Templates', () => {
it('has the description of the template', () => {
expect(findTemplateDescriptions().at(0).text()).toBe(
'CI/CD template to test and deploy your Android project.',
sprintf(I18N.templates.description, { name: 'Android' }),
);
});
......@@ -104,4 +123,73 @@ describe('Pipelines CI Templates', () => {
});
});
});
describe('when the runners_availability_section experiment is active', () => {
beforeEach(() => {
stubExperiments({ runners_availability_section: 'candidate' });
});
describe('when runners are available', () => {
beforeEach(() => {
wrapper = createWrapper({ anyRunnersAvailable: true }, { GitlabExperiment, GlSprintf });
});
it('renders the templates', () => {
expect(findTestTemplateLinks().exists()).toBe(true);
expect(findTemplateLinks().exists()).toBe(true);
});
it('show the runners available section', () => {
expect(wrapper.text()).toContain(I18N.runners.title);
});
it('tracks an event when clicking the settings link', () => {
findSettingsLink().vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith(
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
);
});
it('tracks an event when clicking the documentation link', () => {
findDocumentationLink().vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith(
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
);
});
});
describe('when runners are not available', () => {
beforeEach(() => {
wrapper = createWrapper({ anyRunnersAvailable: false }, { GitlabExperiment, GlButton });
});
it('does not render the templates', () => {
expect(findTestTemplateLinks().exists()).toBe(false);
expect(findTemplateLinks().exists()).toBe(false);
});
it('show the no runners available section', () => {
expect(wrapper.text()).toContain(I18N.noRunners.title);
});
it('tracks an event when clicking the settings button', () => {
findSettingsButton().trigger('click');
expect(ExperimentTracking).toHaveBeenCalledWith(
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
);
});
});
});
});
......@@ -93,4 +93,63 @@ RSpec.describe Ci::PipelinesHelper do
end
end
end
describe '#pipelines_list_data' do
let_it_be(:project) { create(:project) }
subject(:data) { helper.pipelines_list_data(project, 'list_url') }
before do
allow(helper).to receive(:can?).and_return(true)
end
it 'has the expected keys' do
expect(subject.keys).to match_array([:endpoint,
:project_id,
:params,
:artifacts_endpoint,
:artifacts_endpoint_placeholder,
:pipeline_schedule_url,
:empty_state_svg_path,
:error_state_svg_path,
:no_pipelines_svg_path,
:can_create_pipeline,
:new_pipeline_path,
:ci_lint_path,
:reset_cache_path,
:has_gitlab_ci,
:pipeline_editor_path,
:suggested_ci_templates,
:code_quality_page_path,
:ci_runner_settings_path])
end
describe 'the `any_runners_available` attribute' do
subject { data[:any_runners_available] }
context 'when the `runners_availability_section` experiment variant is control' do
before do
stub_experiments(runners_availability_section: :control)
end
it { is_expected.to be_nil }
end
context 'when the `runners_availability_section` experiment variant is candidate' do
before do
stub_experiments(runners_availability_section: :candidate)
end
context 'when there are no runners' do
it { is_expected.to eq('false') }
end
context 'when there are runners' do
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it { is_expected.to eq('true') }
end
end
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