Commit 079e4368 authored by Alex Buijs's avatar Alex Buijs Committed by Dylan Griffith

Experiment: Pipelines Empty State Runner Templates [RUN ALL RSPEC] [RUN AS-IF-FOSS]

parent a7015b36
...@@ -3,6 +3,7 @@ import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners ...@@ -3,6 +3,7 @@ import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners
import initSharedRunnersForm from '~/group_settings/mount_shared_runners'; import initSharedRunnersForm from '~/group_settings/mount_shared_runners';
import { FILTERED_SEARCH } from '~/pages/constants'; import { FILTERED_SEARCH } from '~/pages/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search'; import initFilteredSearch from '~/pages/search/init_filtered_search';
import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deployments';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions'; import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
...@@ -20,3 +21,4 @@ initSharedRunnersForm(); ...@@ -20,3 +21,4 @@ initSharedRunnersForm();
initVariableList(); initVariableList();
initInstallRunner(); initInstallRunner();
initRunnerAwsDeployments();
...@@ -4,6 +4,7 @@ import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers'; ...@@ -4,6 +4,7 @@ import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initVariableList from '~/ci_variable_list'; import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze'; import initDeployFreeze from '~/deploy_freeze';
import registrySettingsApp from '~/packages_and_registries/settings/project/registry_settings_bundle'; import registrySettingsApp from '~/packages_and_registries/settings/project/registry_settings_bundle';
import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deployments';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions'; import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle'; import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
...@@ -38,4 +39,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -38,4 +39,5 @@ document.addEventListener('DOMContentLoaded', () => {
initArtifactsSettings(); initArtifactsSettings();
initSharedRunnersToggle(); initSharedRunnersToggle();
initInstallRunner(); initInstallRunner();
initRunnerAwsDeployments();
}); });
import Vue from 'vue';
import RunnerAwsDeployments from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue';
export function initRunnerAwsDeployments(componentId = 'js-runner-aws-deployments') {
const el = document.getElementById(componentId);
if (!el) {
return null;
}
return new Vue({
el,
render(createElement) {
return createElement(RunnerAwsDeployments);
},
});
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { GlEmptyState, GlButton } from '@gitlab/ui'; import { GlEmptyState, GlButton } from '@gitlab/ui';
import { startCodeQualityWalkthrough, track } from '~/code_quality_walkthrough/utils'; import { startCodeQualityWalkthrough, track } from '~/code_quality_walkthrough/utils';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue'; import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { getExperimentData } from '~/experimentation/utils'; import { getExperimentData } from '~/experimentation/utils';
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -13,7 +14,9 @@ export default { ...@@ -13,7 +14,9 @@ export default {
description: s__(`Pipelines|GitLab CI/CD can automatically build, description: s__(`Pipelines|GitLab CI/CD can automatically build,
test, and deploy your code. Let GitLab take care of time test, and deploy your code. Let GitLab take care of time
consuming tasks, so you can spend more time creating.`), consuming tasks, so you can spend more time creating.`),
btnText: s__('Pipelines|Get started with CI/CD'), aboutRunnersBtnText: s__('Pipelines|Learn about 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
...@@ -42,6 +45,11 @@ export default { ...@@ -42,6 +45,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
ciRunnerSettingsPath: {
type: String,
required: false,
default: null,
},
}, },
computed: { computed: {
ciHelpPagePath() { ciHelpPagePath() {
...@@ -50,6 +58,12 @@ export default { ...@@ -50,6 +58,12 @@ export default {
isPipelineEmptyStateTemplatesExperimentActive() { isPipelineEmptyStateTemplatesExperimentActive() {
return this.canSetCi && Boolean(getExperimentData('pipeline_empty_state_templates')); return this.canSetCi && Boolean(getExperimentData('pipeline_empty_state_templates'));
}, },
isCodeQualityExperimentActive() {
return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough'));
},
isCiRunnerTemplatesExperimentActive() {
return this.canSetCi && Boolean(getExperimentData('ci_runner_templates'));
},
}, },
mounted() { mounted() {
startCodeQualityWalkthrough(); startCodeQualityWalkthrough();
...@@ -58,6 +72,10 @@ export default { ...@@ -58,6 +72,10 @@ export default {
trackClick() { trackClick() {
track('cta_clicked'); track('cta_clicked');
}, },
trackCiRunnerTemplatesClick(action) {
const tracking = new ExperimentTracking('ci_runner_templates');
tracking.event(action);
},
}, },
}; };
</script> </script>
...@@ -72,7 +90,7 @@ export default { ...@@ -72,7 +90,7 @@ export default {
:title="$options.i18n.title" :title="$options.i18n.title"
:svg-path="emptyStateSvgPath" :svg-path="emptyStateSvgPath"
:description="$options.i18n.description" :description="$options.i18n.description"
:primary-button-text="$options.i18n.btnText" :primary-button-text="$options.i18n.getStartedBtnText"
:primary-button-link="ciHelpPagePath" :primary-button-link="ciHelpPagePath"
/> />
</template> </template>
...@@ -80,7 +98,7 @@ export default { ...@@ -80,7 +98,7 @@ export default {
<pipelines-ci-templates /> <pipelines-ci-templates />
</template> </template>
</gitlab-experiment> </gitlab-experiment>
<gitlab-experiment v-else-if="canSetCi" name="code_quality_walkthrough"> <gitlab-experiment v-else-if="isCodeQualityExperimentActive" name="code_quality_walkthrough">
<template #control> <template #control>
<gl-empty-state <gl-empty-state
:title="$options.i18n.title" :title="$options.i18n.title"
...@@ -89,7 +107,7 @@ export default { ...@@ -89,7 +107,7 @@ export default {
> >
<template #actions> <template #actions>
<gl-button :href="ciHelpPagePath" variant="confirm" @click="trackClick()"> <gl-button :href="ciHelpPagePath" variant="confirm" @click="trackClick()">
{{ $options.i18n.btnText }} {{ $options.i18n.getStartedBtnText }}
</gl-button> </gl-button>
</template> </template>
</gl-empty-state> </gl-empty-state>
...@@ -108,6 +126,57 @@ export default { ...@@ -108,6 +126,57 @@ export default {
</gl-empty-state> </gl-empty-state>
</template> </template>
</gitlab-experiment> </gitlab-experiment>
<gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates">
<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="trackCiRunnerTemplatesClick('get_started_button_clicked')"
>
{{ $options.i18n.getStartedBtnText }}
</gl-button>
</template>
</gl-empty-state>
</template>
<template #candidate>
<gl-empty-state
:title="$options.i18n.title"
:svg-path="emptyStateSvgPath"
:description="$options.i18n.description"
>
<template #actions>
<gl-button
:href="ciRunnerSettingsPath"
variant="confirm"
@click="trackCiRunnerTemplatesClick('install_runners_button_clicked')"
>
{{ $options.i18n.installRunnersBtnText }}
</gl-button>
<gl-button
:href="ciHelpPagePath"
variant="default"
@click="trackCiRunnerTemplatesClick('learn_button_clicked')"
>
{{ $options.i18n.aboutRunnersBtnText }}
</gl-button>
</template>
</gl-empty-state>
</template>
</gitlab-experiment>
<gl-empty-state
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=""
......
...@@ -99,6 +99,11 @@ export default { ...@@ -99,6 +99,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
ciRunnerSettingsPath: {
type: String,
required: false,
default: null,
},
}, },
data() { data() {
return { return {
...@@ -345,6 +350,7 @@ export default { ...@@ -345,6 +350,7 @@ export default {
:empty-state-svg-path="emptyStateSvgPath" :empty-state-svg-path="emptyStateSvgPath"
:can-set-ci="canCreatePipeline" :can-set-ci="canCreatePipeline"
:code-quality-page-path="codeQualityPagePath" :code-quality-page-path="codeQualityPagePath"
:ci-runner-settings-path="ciRunnerSettingsPath"
/> />
<gl-empty-state <gl-empty-state
......
...@@ -38,6 +38,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { ...@@ -38,6 +38,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
projectId, projectId,
params, params,
codeQualityPagePath, codeQualityPagePath,
ciRunnerSettingsPath,
} = el.dataset; } = el.dataset;
return new Vue({ return new Vue({
...@@ -76,6 +77,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { ...@@ -76,6 +77,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
projectId, projectId,
params: JSON.parse(params), params: JSON.parse(params),
codeQualityPagePath, codeQualityPagePath,
ciRunnerSettingsPath,
}, },
}); });
}, },
......
import { s__, sprintf } from '~/locale';
export const EXPERIMENT_NAME = 'ci_runner_templates';
export const README_URL =
'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md';
export const CF_BASE_URL =
'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?';
export const TEMPLATES_BASE_URL = 'https://gl-public-templates.s3.amazonaws.com/cfn/experimental/';
export const EASY_BUTTONS = [
{
stackName: 'linux-docker-nonspot',
templateName:
'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml',
description: s__(
'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.',
),
},
{
stackName: 'linux-docker-spotonly',
templateName: 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml',
description: sprintf(
s__(
'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot.',
),
{ percentage: '100%' },
),
},
{
stackName: 'win2019-shell-non-spot',
templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml',
description: s__(
'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.',
),
},
{
stackName: 'win2019-shell-spot',
templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml',
description: sprintf(
s__(
'Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot.',
),
{ percentage: '100%' },
),
},
];
<script>
import { GlButton, GlModalDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import RunnerAwsDeploymentsModal from './runner_aws_deployments_modal.vue';
export default {
components: {
GlButton,
RunnerAwsDeploymentsModal,
},
directives: {
GlModalDirective,
},
modalId: 'runner-aws-deployments-modal',
i18n: {
buttonText: s__('Runners|Deploy GitLab Runner in AWS'),
},
data() {
return {
opened: false,
};
},
methods: {
onClick() {
this.opened = true;
},
},
};
</script>
<template>
<div>
<gl-button
v-gl-modal-directive="$options.modalId"
class="gl-mt-4"
data-testid="show-modal-button"
variant="confirm"
@click="onClick"
>
{{ $options.i18n.buttonText }}
</gl-button>
<runner-aws-deployments-modal v-if="opened" :modal-id="$options.modalId" />
</div>
</template>
<script>
import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { getBaseURL, objectToQuery } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import {
EXPERIMENT_NAME,
README_URL,
CF_BASE_URL,
TEMPLATES_BASE_URL,
EASY_BUTTONS,
} from './constants';
export default {
components: {
GlModal,
GlSprintf,
GlLink,
},
props: {
modalId: {
type: String,
required: true,
},
},
methods: {
easyButtonUrl(easyButton) {
const params = {
templateURL: TEMPLATES_BASE_URL + easyButton.templateName,
stackName: easyButton.stackName,
param_3GITLABRunnerInstanceURL: getBaseURL(),
};
return CF_BASE_URL + objectToQuery(params);
},
trackCiRunnerTemplatesClick(stackName) {
const tracking = new ExperimentTracking(EXPERIMENT_NAME);
tracking.event(`template_clicked_${stackName}`);
},
},
i18n: {
title: s__('Runners|Deploy GitLab Runner in AWS'),
instructions: s__(
'Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.',
),
dont_see_what_you_are_looking_for: s__(
"Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}.",
),
note: s__(
'Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.',
),
},
closeButton: {
text: __('Cancel'),
attributes: [{ variant: 'default' }],
},
readmeUrl: README_URL,
easyButtons: EASY_BUTTONS,
};
</script>
<template>
<gl-modal
:modal-id="modalId"
:title="$options.i18n.title"
:action-secondary="$options.closeButton"
size="sm"
>
<p>{{ $options.i18n.instructions }}</p>
<ul class="gl-list-style-none gl-p-0 gl-mb-0">
<li v-for="easyButton in $options.easyButtons" :key="easyButton.templateName">
<gl-link
:href="easyButtonUrl(easyButton)"
target="_blank"
class="gl-display-flex gl-font-weight-bold"
@click="trackCiRunnerTemplatesClick(easyButton.stackName)"
>
<img
:title="easyButton.stackName"
:alt="easyButton.stackName"
src="/assets/aws-cloud-formation.png"
width="46"
height="46"
class="gl-mt-2 gl-mr-5 gl-mb-6"
/>
{{ easyButton.description }}
</gl-link>
</li>
</ul>
<p>
<gl-sprintf :message="$options.i18n.dont_see_what_you_are_looking_for">
<template #link="{ content }">
<gl-link :href="$options.readmeUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p class="gl-font-sm gl-mb-0">{{ $options.i18n.note }}</p>
</gl-modal>
</template>
...@@ -49,26 +49,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -49,26 +49,9 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
experiment(:pipeline_empty_state_templates, namespace: project.root_ancestor) do |e| enable_pipeline_empty_state_templates_experiment
e.exclude! unless current_user enable_code_quality_walkthrough_experiment
e.exclude! if @pipelines_count.to_i > 0 enable_ci_runner_templates_experiment
e.exclude! if helpers.has_gitlab_ci?(project)
e.use {}
e.try {}
e.record!
end
experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e|
e.exclude! unless current_user
e.exclude! unless can?(current_user, :create_pipeline, project)
e.exclude! unless project.root_ancestor.recent?
e.exclude! if @pipelines_count.to_i > 0
e.exclude! if helpers.has_gitlab_ci?(project)
e.use {}
e.try {}
e.record!
end
end end
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL) Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
...@@ -317,6 +300,45 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -317,6 +300,45 @@ class Projects::PipelinesController < Projects::ApplicationController
def index_params def index_params
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
experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e|
e.exclude! unless current_user
e.exclude! unless can?(current_user, :create_pipeline, project)
e.exclude! unless project.root_ancestor.recent?
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_ci_runner_templates_experiment
experiment(:ci_runner_templates, namespace: project.root_ancestor) do |e|
e.exclude! unless current_user
e.exclude! unless can?(current_user, :create_pipeline, project)
e.exclude! if @pipelines_count.to_i > 0
e.exclude! if helpers.has_gitlab_ci?(project)
e.control {}
e.candidate {}
e.record!
end
end
end end
Projects::PipelinesController.prepend_mod_with('Projects::PipelinesController') Projects::PipelinesController.prepend_mod_with('Projects::PipelinesController')
%h5= _('Use GitLab Runner in AWS')
%p
= _('Use an AWS CloudFormation Template (CFT) to install and configure GitLab Runner in AWS.')
%ol
%li
= _('Copy this registration token.')
%br
%code#registration_token{ data: { testid: 'registration_token' } }= registration_token
= clipboard_button(target: '#registration_token', title: _('Copy token'), class: 'btn-transparent btn-clipboard')
%li
= _('Choose the preferred Runner and populate the AWS CFT.')
= link_to _('Learn more.'), 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg', target: '_blank', rel: 'noopener noreferrer'
#js-runner-aws-deployments
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
= render partial: 'ci/runner/how_to_setup_runner_automatically', = render partial: 'ci/runner/how_to_setup_runner_automatically',
locals: { type: 'group', locals: { type: 'group',
clusters_path: group_clusters_path(@group) } clusters_path: group_clusters_path(@group) }
- if params[:ci_runner_templates]
%hr
= render partial: 'ci/runner/setup_runner_in_aws',
locals: { registration_token: @group.runners_token }
%hr %hr
= render partial: 'ci/runner/how_to_setup_runner', = render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @group.runners_token, locals: { registration_token: @group.runners_token,
......
...@@ -24,4 +24,5 @@ ...@@ -24,4 +24,5 @@
"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, "add-ci-yml-path" => can?(current_user, :create_pipeline, @project) && @project.present(current_user: current_user).add_ci_yml_path,
"suggested-ci-templates" => experiment_suggested_ci_templates.to_json, "suggested-ci-templates" => experiment_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') } }
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
= render partial: 'ci/runner/how_to_setup_runner_automatically', = render partial: 'ci/runner/how_to_setup_runner_automatically',
locals: { type: 'specific', locals: { type: 'specific',
clusters_path: project_clusters_path(@project) } clusters_path: project_clusters_path(@project) }
- if params[:ci_runner_templates]
%hr
= render partial: 'ci/runner/setup_runner_in_aws',
locals: { registration_token: @project.runners_token }
%hr %hr
= render partial: 'ci/runner/how_to_setup_runner', = render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @project.runners_token, locals: { registration_token: @project.runners_token,
......
---
name: ci_runner_templates
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58357
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326725
milestone: "14.0"
type: experiment
group: group::activation
default_enabled: false
...@@ -6466,6 +6466,9 @@ msgstr "" ...@@ -6466,6 +6466,9 @@ msgstr ""
msgid "Choose specific groups or storage shards" msgid "Choose specific groups or storage shards"
msgstr "" msgstr ""
msgid "Choose the preferred Runner and populate the AWS CFT."
msgstr ""
msgid "Choose the top-level group for your repository imports." msgid "Choose the top-level group for your repository imports."
msgstr "" msgstr ""
...@@ -9030,6 +9033,9 @@ msgstr "" ...@@ -9030,6 +9033,9 @@ msgstr ""
msgid "Copy the code below to implement tracking in your application:" msgid "Copy the code below to implement tracking in your application:"
msgstr "" msgstr ""
msgid "Copy this registration token."
msgstr ""
msgid "Copy this value" msgid "Copy this value"
msgstr "" msgstr ""
...@@ -24158,12 +24164,18 @@ msgstr "" ...@@ -24158,12 +24164,18 @@ msgstr ""
msgid "Pipelines|Improve code quality with GitLab CI/CD" msgid "Pipelines|Improve code quality with GitLab CI/CD"
msgstr "" msgstr ""
msgid "Pipelines|Install GitLab Runners"
msgstr ""
msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource." msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource."
msgstr "" msgstr ""
msgid "Pipelines|Last Used" msgid "Pipelines|Last Used"
msgstr "" msgstr ""
msgid "Pipelines|Learn about Runners"
msgstr ""
msgid "Pipelines|Lint" msgid "Pipelines|Lint"
msgstr "" msgstr ""
...@@ -28090,6 +28102,9 @@ msgstr "" ...@@ -28090,6 +28102,9 @@ msgstr ""
msgid "RightSidebar|deleting the" msgid "RightSidebar|deleting the"
msgstr "" msgstr ""
msgid "Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}."
msgstr ""
msgid "Roadmap" msgid "Roadmap"
msgstr "" msgstr ""
...@@ -28168,6 +28183,12 @@ msgstr "" ...@@ -28168,6 +28183,12 @@ msgstr ""
msgid "Runners|Active" msgid "Runners|Active"
msgstr "" msgstr ""
msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot."
msgstr ""
msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor."
msgstr ""
msgid "Runners|An error has occurred fetching instructions" msgid "Runners|An error has occurred fetching instructions"
msgstr "" msgstr ""
...@@ -28186,6 +28207,9 @@ msgstr "" ...@@ -28186,6 +28207,9 @@ msgstr ""
msgid "Runners|Copy instructions" msgid "Runners|Copy instructions"
msgstr "" msgstr ""
msgid "Runners|Deploy GitLab Runner in AWS"
msgstr ""
msgid "Runners|Description" msgid "Runners|Description"
msgstr "" msgstr ""
...@@ -28198,9 +28222,15 @@ msgstr "" ...@@ -28198,9 +28222,15 @@ msgstr ""
msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project." msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project."
msgstr "" msgstr ""
msgid "Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet."
msgstr ""
msgid "Runners|IP Address" msgid "Runners|IP Address"
msgstr "" msgstr ""
msgid "Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account."
msgstr ""
msgid "Runners|Install a runner" msgid "Runners|Install a runner"
msgstr "" msgstr ""
...@@ -28294,6 +28324,12 @@ msgstr "" ...@@ -28294,6 +28324,12 @@ msgstr ""
msgid "Runners|View installation instructions" msgid "Runners|View installation instructions"
msgstr "" msgstr ""
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot."
msgstr ""
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor."
msgstr ""
msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared runner." msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared runner."
msgstr "" msgstr ""
...@@ -35352,9 +35388,15 @@ msgstr "" ...@@ -35352,9 +35388,15 @@ msgstr ""
msgid "Use .gitlab-ci.yml" msgid "Use .gitlab-ci.yml"
msgstr "" msgstr ""
msgid "Use GitLab Runner in AWS"
msgstr ""
msgid "Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)." msgid "Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
msgstr "" msgstr ""
msgid "Use an AWS CloudFormation Template (CFT) to install and configure GitLab Runner in AWS."
msgstr ""
msgid "Use cURL" msgid "Use cURL"
msgstr "" msgstr ""
......
...@@ -278,26 +278,22 @@ RSpec.describe Projects::PipelinesController do ...@@ -278,26 +278,22 @@ RSpec.describe Projects::PipelinesController do
stub_application_setting(auto_devops_enabled: false) stub_application_setting(auto_devops_enabled: false)
end end
context 'pipeline_empty_state_templates experiment' do def action
it 'tracks the assignment', :experiment do get :index, params: { namespace_id: project.namespace, project_id: project }
expect(experiment(:pipeline_empty_state_templates)) end
.to track(:assignment)
.with_context(namespace: project.root_ancestor)
.on_next_instance
get :index, params: { namespace_id: project.namespace, project_id: project } subject { project.namespace }
end
context 'pipeline_empty_state_templates experiment' do
it_behaves_like 'tracks assignment and records the subject', :pipeline_empty_state_templates, :namespace
end end
context 'code_quality_walkthrough experiment' do context 'code_quality_walkthrough experiment' do
it 'tracks the assignment', :experiment do it_behaves_like 'tracks assignment and records the subject', :code_quality_walkthrough, :namespace
expect(experiment(:code_quality_walkthrough)) end
.to track(:assignment)
.with_context(namespace: project.root_ancestor)
.on_next_instance
get :index, params: { namespace_id: project.namespace, project_id: project } context 'ci_runner_templates experiment' do
end it_behaves_like 'tracks assignment and records the subject', :ci_runner_templates, :namespace
end end
end end
......
...@@ -7,7 +7,7 @@ import { nextTick } from 'vue'; ...@@ -7,7 +7,7 @@ import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api'; import Api from '~/api';
import { getExperimentVariant } from '~/experimentation/utils'; import { getExperimentData, getExperimentVariant } from '~/experimentation/utils';
import createFlash from '~/flash'; 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';
...@@ -23,6 +23,7 @@ import { stageReply, users, mockSearch, branches } from './mock_data'; ...@@ -23,6 +23,7 @@ import { stageReply, users, mockSearch, branches } from './mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('~/experimentation/utils', () => ({ jest.mock('~/experimentation/utils', () => ({
...jest.requireActual('~/experimentation/utils'), ...jest.requireActual('~/experimentation/utils'),
getExperimentData: jest.fn().mockReturnValue(false),
getExperimentVariant: jest.fn().mockReturnValue('control'), getExperimentVariant: jest.fn().mockReturnValue('control'),
})); }));
...@@ -48,6 +49,7 @@ describe('Pipelines', () => { ...@@ -48,6 +49,7 @@ describe('Pipelines', () => {
resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`, resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`,
newPipelinePath: `${mockProjectPath}/pipelines/new`, newPipelinePath: `${mockProjectPath}/pipelines/new`,
codeQualityPagePath: `${mockProjectPath}/-/new/master?commit_message=Add+.gitlab-ci.yml+and+create+a+code+quality+job&file_name=.gitlab-ci.yml&template=Code-Quality`, codeQualityPagePath: `${mockProjectPath}/-/new/master?commit_message=Add+.gitlab-ci.yml+and+create+a+code+quality+job&file_name=.gitlab-ci.yml&template=Code-Quality`,
ciRunnerSettingsPath: `${mockProjectPath}/-/settings/ci_cd#js-runners-settings`,
}; };
const noPermissions = { const noPermissions = {
...@@ -563,6 +565,7 @@ describe('Pipelines', () => { ...@@ -563,6 +565,7 @@ describe('Pipelines', () => {
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');
getExperimentVariant.mockReturnValue('candidate'); getExperimentVariant.mockReturnValue('candidate');
}); });
...@@ -574,6 +577,29 @@ describe('Pipelines', () => { ...@@ -574,6 +577,29 @@ describe('Pipelines', () => {
}); });
}); });
describe('when the ci_runner_templates experiment is active', () => {
beforeAll(() => {
getExperimentData.mockImplementation((name) => name === 'ci_runner_templates');
getExperimentVariant.mockReturnValue('candidate');
});
it('renders two buttons', () => {
expect(findEmptyState().findAllComponents(GlButton).length).toBe(2);
expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe(
'Install GitLab Runners',
);
expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe(
paths.ciRunnerSettingsPath,
);
expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe(
'Learn about Runners',
);
expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe(
'/help/ci/quick_start/index.md',
);
});
});
it('does not render filtered search', () => { it('does not render filtered search', () => {
expect(findFilteredSearch().exists()).toBe(false); expect(findFilteredSearch().exists()).toBe(false);
}); });
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RunnerAwsDeploymentsModal renders the modal 1`] = `
<gl-modal-stub
actionsecondary="[object Object]"
dismisslabel="Close"
modalclass=""
modalid="runner-aws-deployments-modal"
size="sm"
title="Deploy GitLab Runner in AWS"
titletag="h4"
>
<p>
For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.
</p>
<ul
class="gl-list-style-none gl-p-0 gl-mb-0"
>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml&stackName=linux-docker-nonspot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
>
<img
alt="linux-docker-nonspot"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="linux-docker-nonspot"
width="46"
/>
Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.
</gl-link-stub>
</li>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml&stackName=linux-docker-spotonly&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
>
<img
alt="linux-docker-spotonly"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="linux-docker-spotonly"
width="46"
/>
Amazon Linux 2 Docker HA with manual scaling and optional scheduling. 100% spot.
</gl-link-stub>
</li>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml&stackName=win2019-shell-non-spot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
>
<img
alt="win2019-shell-non-spot"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="win2019-shell-non-spot"
width="46"
/>
Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.
</gl-link-stub>
</li>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml&stackName=win2019-shell-spot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
>
<img
alt="win2019-shell-spot"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="win2019-shell-spot"
width="46"
/>
Windows 2019 Shell with manual scaling and optional scheduling. 100% spot.
</gl-link-stub>
</li>
</ul>
<p>
<gl-sprintf-stub
message="Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}."
/>
</p>
<p
class="gl-font-sm gl-mb-0"
>
If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.
</p>
</gl-modal-stub>
`;
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { getBaseURL } from '~/lib/utils/url_utility';
import {
EXPERIMENT_NAME,
CF_BASE_URL,
TEMPLATES_BASE_URL,
EASY_BUTTONS,
} from '~/vue_shared/components/runner_aws_deployments/constants';
import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
jest.mock('~/experimentation/experiment_tracking');
describe('RunnerAwsDeploymentsModal', () => {
let wrapper;
const findEasyButtons = () => wrapper.findAllComponents(GlLink);
const createComponent = () => {
wrapper = shallowMount(RunnerAwsDeploymentsModal, {
propsData: {
modalId: 'runner-aws-deployments-modal',
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the modal', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('should contain all easy buttons', () => {
expect(findEasyButtons()).toHaveLength(EASY_BUTTONS.length);
});
describe('first easy button', () => {
const findFirstButton = () => findEasyButtons().at(0);
it('should contain the correct description', () => {
expect(findFirstButton().text()).toBe(EASY_BUTTONS[0].description);
});
it('should contain the correct link', () => {
const link = findFirstButton().attributes('href');
expect(link.startsWith(CF_BASE_URL)).toBe(true);
expect(
link.includes(
`templateURL=${encodeURIComponent(TEMPLATES_BASE_URL + EASY_BUTTONS[0].templateName)}`,
),
).toBe(true);
expect(link.includes(`stackName=${EASY_BUTTONS[0].stackName}`)).toBe(true);
expect(
link.includes(`param_3GITLABRunnerInstanceURL=${encodeURIComponent(getBaseURL())}`),
).toBe(true);
});
it('should track an event when clicked', () => {
findFirstButton().vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith(EXPERIMENT_NAME);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
`template_clicked_${EASY_BUTTONS[0].stackName}`,
);
});
});
});
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import RunnerAwsDeployments from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue';
import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
describe('RunnerAwsDeployments component', () => {
let wrapper;
const findModalButton = () => wrapper.findByTestId('show-modal-button');
const findModal = () => wrapper.findComponent(RunnerAwsDeploymentsModal);
const createComponent = () => {
wrapper = extendedWrapper(shallowMount(RunnerAwsDeployments));
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('should show the "Deploy GitLab Runner in AWS" button', () => {
expect(findModalButton().exists()).toBe(true);
expect(findModalButton().text()).toBe('Deploy GitLab Runner in AWS');
});
it('should not render the modal once mounted', () => {
expect(findModal().exists()).toBe(false);
});
it('should render the modal once clicked', async () => {
findModalButton().vm.$emit('click');
await nextTick();
expect(findModal().exists()).toBe(true);
});
});
# frozen_string_literal: true
RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type|
it 'tracks the assignment', :experiment do
expect(experiment(experiment))
.to track(:assignment)
.with_context(subject_type => subject)
.on_next_instance
action
end
it 'records the subject' do
stub_experiments(experiment => :candidate)
expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: :experimental, subject: subject)
action
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