Commit 8b54a3f3 authored by Jose Vargas's avatar Jose Vargas

Move runner installation logic to GraphQL

This removes the Vuex logic and uses Apollo
instead for state management
parent 6409921e
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import InstallRunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; import InstallRunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import { createStore } from '~/vue_shared/components/runner_instructions/store';
Vue.use(VueApollo);
export function initInstallRunner() { export function initInstallRunner() {
const installRunnerEl = document.getElementById('js-install-runner'); const installRunnerEl = document.getElementById('js-install-runner');
const { projectPath, groupPath } = installRunnerEl.dataset || {};
if (installRunnerEl) { if (installRunnerEl) {
const defaultClient = createDefaultClient();
const apolloProvider = new VueApollo({
defaultClient,
});
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: installRunnerEl, el: installRunnerEl,
store: createStore({ apolloProvider,
...installRunnerEl.dataset, provide: {
}), projectPath,
groupPath,
},
render(createElement) { render(createElement) {
return createElement(InstallRunnerInstructions); return createElement(InstallRunnerInstructions);
}, },
......
query getProjectId($projectPath: ID!) {
project(fullPath: $projectPath) {
id
}
}
query getRunnerPlatforms {
runnerPlatforms {
nodes {
name
humanReadableName
architectures {
nodes {
name
downloadLocation
}
}
}
}
}
query runnerSetupInstructions($platform: String!, $architecture: String!, $projectId: ID!) {
runnerSetup(platform: $platform, architecture: $architecture, projectId: $projectId) {
installInstructions
registerInstructions
}
}
query runnerSetupInstructionsGroup($platform: String!, $architecture: String!) {
runnerSetup(platform: $platform, architecture: $architecture) {
installInstructions
registerInstructions
}
}
query runnerSetupInstructionsGroup($platform: String!, $architecture: String!, $groupId: ID!) {
runnerSetup(platform: $platform, architecture: $architecture, groupId: $groupId) {
installInstructions
registerInstructions
}
}
...@@ -9,8 +9,13 @@ import { ...@@ -9,8 +9,13 @@ import {
GlDropdownItem, GlDropdownItem,
GlIcon, GlIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import getRunnerPlatforms from './graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructions from './graphql/queries/get_runner_setup.query.graphql';
import getRunnerSetupInstructionsGroup from './graphql/queries/get_runner_setup_group.query.graphql';
import getRunnerSetupInstructionsAdmin from './graphql/queries/get_runner_setup_admin.query.graphql';
import getProjectId from './graphql/queries/get_project_id.query.graphql';
import getGroupId from './graphql/queries/get_group_id.query.graphql';
export default { export default {
components: { components: {
...@@ -25,39 +30,127 @@ export default { ...@@ -25,39 +30,127 @@ export default {
directives: { directives: {
GlModalDirective, GlModalDirective,
}, },
inject: {
projectPath: {
default: '',
},
groupPath: {
default: '',
},
},
apollo: {
runnerPlatforms: {
query: getRunnerPlatforms,
update(data) {
return data?.runnerPlatforms?.nodes;
},
error() {
this.showAlert = true;
},
},
projectId: {
query: getProjectId,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
return data?.project?.id;
},
error() {
this.showAlert = true;
},
},
groupId: {
query: getGroupId,
variables() {
return {
groupPath: this.groupPath,
};
},
update(data) {
return data?.group?.id;
},
error() {
this.showAlert = true;
},
},
},
data() {
return {
showAlert: false,
selectedPlatformArchitectures: [],
selectedPlatform: {},
selectedArchitecture: {},
runnerPlatforms: [],
projectId: '',
groupId: '',
instructions: {},
};
},
computed: { computed: {
...mapState('installRunnerPopup', [
'availablePlatforms',
'instructions',
'selectedArchitecture',
'showAlert',
]),
...mapGetters('installRunnerPopup', [
'getSupportedArchitectures',
'instructionsEmpty',
'hasDownloadLocationsAvailable',
'getDownloadLocation',
]),
closeButton() { closeButton() {
return { return {
text: __('Close'), text: __('Close'),
attributes: [{ variant: 'default' }], attributes: [{ variant: 'default' }],
}; };
}, },
isArchitectureSelected() { isPlatformSelected() {
return this.selectedArchitecture !== ''; return Object.keys(this.selectedPlatform).length > 0;
},
instructionsEmpty() {
return this.instructions == null || Object.keys(this.instructions).length === 0;
}, },
},
mounted() {
this.requestPlatforms();
}, },
methods: { methods: {
...mapActions('installRunnerPopup', [ selectPlatform(name) {
'requestPlatforms', this.selectedPlatform = this.runnerPlatforms.find(platform => platform.name === name);
'selectPlatform', this.selectedPlatformArchitectures = this.selectedPlatform?.architectures?.nodes;
'startInstructionsRequest', [this.selectedArchitecture] = this.selectedPlatformArchitectures;
'toggleAlert', this.selectArchitecture(this.selectedArchitecture.name);
]), },
selectArchitecture(name) {
this.selectedArchitecture = this.selectedPlatformArchitectures.find(
architecture => architecture.name === name,
);
this.$apollo.addSmartQuery('instructions', {
variables() {
const vars = {
platform: this.selectedPlatform.name,
architecture: this.selectedArchitecture.name,
};
if (this.projectId) {
vars.projectId = this.projectId;
}
if (this.groupId) {
vars.groupId = this.groupId;
}
return vars;
},
query() {
if (this.projectId) {
return getRunnerSetupInstructions;
} else if (this.groupId) {
return getRunnerSetupInstructionsGroup;
}
return getRunnerSetupInstructionsAdmin;
},
update(data) {
return data?.runnerSetup;
},
error() {
this.showAlert = true;
},
});
},
toggleAlert(state) {
this.showAlert = state;
},
}, },
modalId: 'installation-instructions-modal', modalId: 'installation-instructions-modal',
i18n: { i18n: {
...@@ -68,13 +161,14 @@ export default { ...@@ -68,13 +161,14 @@ export default {
registerRunner: s__('Runners|Register Runner'), registerRunner: s__('Runners|Register Runner'),
method: __('Method'), method: __('Method'),
genericError: __('An error has occurred'), genericError: __('An error has occurred'),
instructions: __('Show Runner installation instructions'),
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-button v-gl-modal-directive="$options.modalId" data-testid="show-modal-button"> <gl-button v-gl-modal-directive="$options.modalId" data-testid="show-modal-button">
{{ __('Show Runner installation instructions') }} {{ $options.i18n.instructions }}
</gl-button> </gl-button>
<gl-modal <gl-modal
:modal-id="$options.modalId" :modal-id="$options.modalId"
...@@ -87,33 +181,34 @@ export default { ...@@ -87,33 +181,34 @@ export default {
<h5>{{ __('Environment') }}</h5> <h5>{{ __('Environment') }}</h5>
<gl-button-group class="gl-mb-5"> <gl-button-group class="gl-mb-5">
<gl-button <gl-button
v-for="(platform, key) in availablePlatforms" v-for="platform in runnerPlatforms"
:key="key" :key="platform.name"
data-testid="platform-button" data-testid="platform-button"
@click="selectPlatform(key)" @click="selectPlatform(platform.name)"
> >
{{ platform.human_readable_name }} {{ platform.humanReadableName }}
</gl-button> </gl-button>
</gl-button-group> </gl-button-group>
<template v-if="hasDownloadLocationsAvailable"> <template v-if="isPlatformSelected">
<h5> <h5>
{{ $options.i18n.architecture }} {{ $options.i18n.architecture }}
</h5> </h5>
<gl-dropdown class="gl-mb-5" :text="selectedArchitecture"> <gl-dropdown class="gl-mb-5" :text="selectedArchitecture.name">
<gl-dropdown-item <gl-dropdown-item
v-for="(architecture, index) in getSupportedArchitectures" v-for="architecture in selectedPlatformArchitectures"
:key="index" :key="architecture.name"
data-testid="architecture-dropdown-item" data-testid="architecture-dropdown-item"
@click="startInstructionsRequest(architecture)" @click="selectArchitecture(architecture.name)"
> >
{{ architecture }} {{ architecture.name }}
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
<div v-if="isArchitectureSelected" class="gl-display-flex gl-align-items-center gl-mb-5"> <div class="gl-display-flex gl-align-items-center gl-mb-5">
<h5>{{ $options.i18n.downloadInstallBinary }}</h5> <h5>{{ $options.i18n.downloadInstallBinary }}</h5>
<gl-button <gl-button
class="gl-ml-auto" class="gl-ml-auto"
:href="getDownloadLocation" :href="selectedArchitecture.downloadLocation"
download
data-testid="binary-download-button" data-testid="binary-download-button"
> >
{{ $options.i18n.downloadLatestBinary }} {{ $options.i18n.downloadLatestBinary }}
...@@ -123,13 +218,13 @@ export default { ...@@ -123,13 +218,13 @@ export default {
<template v-if="!instructionsEmpty"> <template v-if="!instructionsEmpty">
<div class="gl-display-flex"> <div class="gl-display-flex">
<pre class="bg-light gl-flex-fill-1" data-testid="binary-instructions"> <pre class="bg-light gl-flex-fill-1" data-testid="binary-instructions">
{{ instructions.install.trimStart() }} {{ instructions.installInstructions }}
</pre> </pre>
<gl-button <gl-button
class="gl-align-self-start gl-ml-2 gl-mt-2" class="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary" category="tertiary"
variant="link" variant="link"
:data-clipboard-text="instructions.install" :data-clipboard-text="instructions.installationInstructions"
> >
<gl-icon name="copy-to-clipboard" /> <gl-icon name="copy-to-clipboard" />
</gl-button> </gl-button>
...@@ -140,13 +235,13 @@ export default { ...@@ -140,13 +235,13 @@ export default {
<h5 class="gl-mb-5">{{ $options.i18n.method }}</h5> <h5 class="gl-mb-5">{{ $options.i18n.method }}</h5>
<div class="gl-display-flex"> <div class="gl-display-flex">
<pre class="bg-light gl-flex-fill-1" data-testid="runner-instructions"> <pre class="bg-light gl-flex-fill-1" data-testid="runner-instructions">
{{ instructions.register.trim() }} {{ instructions.registerInstructions }}
</pre> </pre>
<gl-button <gl-button
class="gl-align-self-start gl-ml-2 gl-mt-2" class="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary" category="tertiary"
variant="link" variant="link"
:data-clipboard-text="instructions.register" :data-clipboard-text="instructions.registerInstructions"
> >
<gl-icon name="copy-to-clipboard" /> <gl-icon name="copy-to-clipboard" />
</gl-button> </gl-button>
......
import axios from '~/lib/utils/axios_utils';
import * as types from './mutation_types';
export const requestPlatforms = ({ state, commit, getters, dispatch }) => {
axios
.get(state.platformsPath)
.then(resp => {
commit(types.SET_AVAILABLE_PLATFORMS, resp?.data);
// Select the first platform and architecture
const platform = Object.keys(resp.data)[0];
commit(types.SET_AVAILABLE_PLATFORM, platform);
dispatch('selectArchitecture', getters.getSupportedArchitectures[0]);
dispatch('requestPlatformsInstructions');
})
.catch(() => dispatch('toggleAlert', true));
};
export const requestPlatformsInstructions = ({ commit, state, dispatch }) => {
let path = `${state.instructionsPath}?os=${state.selectedAvailablePlatform}`;
path =
state.selectedArchitecture !== ''
? `${path}&arch=${state.selectedArchitecture}`
: `${path}&arch=amd64`;
axios
.get(path)
.then(resp => commit(types.SET_INSTRUCTIONS, resp?.data))
.catch(() => dispatch('toggleAlert', true));
};
export const startInstructionsRequest = ({ dispatch }, architecture) => {
dispatch('selectArchitecture', architecture);
dispatch('requestPlatformsInstructions');
};
export const selectPlatform = ({ commit, dispatch, getters }, platform) => {
commit(types.SET_AVAILABLE_PLATFORM, platform);
const architecture = getters.getSupportedArchitectures
? getters.getSupportedArchitectures[0]
: '';
dispatch('selectArchitecture', architecture);
dispatch('requestPlatformsInstructions');
};
export const selectArchitecture = ({ commit }, architecture) => {
commit(types.SET_ARCHITECTURE, architecture);
};
export const toggleAlert = ({ commit }, state) => {
commit(types.SET_SHOW_ALERT, state);
};
export const hasDownloadLocationsAvailable = state => {
return state.availablePlatforms[state.selectedAvailablePlatform]?.download_locations;
};
export const getSupportedArchitectures = state => {
return Object.keys(
state.availablePlatforms[state.selectedAvailablePlatform]?.download_locations || {},
);
};
export const instructionsEmpty = state => {
return !Object.keys(state.instructions).length;
};
export const getDownloadLocation = state => {
return state.availablePlatforms[state.selectedAvailablePlatform]?.download_locations[
state.selectedArchitecture
];
};
import Vue from 'vue';
import Vuex from 'vuex';
import createState from './state';
import * as actions from './actions';
import mutations from './mutations';
import * as getters from './getters';
Vue.use(Vuex);
export const createStore = initialState =>
new Vuex.Store({
modules: {
installRunnerPopup: {
namespaced: true,
state: createState(initialState),
actions,
mutations,
getters,
},
},
});
export const SET_AVAILABLE_PLATFORMS = 'SET_AVAILABLE_PLATFORMS';
export const SET_AVAILABLE_PLATFORM = 'SET_AVAILABLE_PLATFORM';
export const SET_ARCHITECTURE = 'SET_ARCHITECTURE';
export const SET_INSTRUCTIONS = 'SET_INSTRUCTIONS';
export const SET_SHOW_ALERT = 'SET_SHOW_ALERT';
import * as types from './mutation_types';
export default {
[types.SET_AVAILABLE_PLATFORMS](state, platforms) {
state.availablePlatforms = platforms;
},
[types.SET_AVAILABLE_PLATFORM](state, index) {
state.selectedAvailablePlatform = index;
},
[types.SET_ARCHITECTURE](state, index) {
state.selectedArchitecture = index;
},
[types.SET_INSTRUCTIONS](state, instructions) {
state.instructions = instructions;
},
[types.SET_SHOW_ALERT](state, show) {
state.showAlert = show;
},
};
export default (initialState = {}) => ({
instructionsPath: initialState.instructionsPath || '',
platformsPath: initialState.platformsPath || '',
availablePlatforms: initialState.availablePlatforms || {},
selectedAvailablePlatform: initialState.selectedAvailablePlatform || '', // index from the availablePlatforms array
selectedArchitecture: initialState.selectedArchitecture || '',
instructions: initialState.instructions || {},
showAlert: false,
});
...@@ -40,8 +40,8 @@ ...@@ -40,8 +40,8 @@
locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token,
type: 'shared', type: 'shared',
reset_token_url: reset_registration_token_admin_application_settings_path, reset_token_url: reset_registration_token_admin_application_settings_path,
instructions_path: runner_setup_scripts_admin_runners_path, project_path: '',
platforms_path: runner_setup_platforms_path } group_path: '' }
.row .row
.col-sm-9 .col-sm-9
......
...@@ -20,4 +20,4 @@ ...@@ -20,4 +20,4 @@
%li %li
= _("Start the Runner!") = _("Start the Runner!")
#js-install-runner{ data: { instructions_path: instructions_path, platforms_path: platforms_path } } #js-install-runner{ data: { project_path: project_path, group_path: group_path } }
...@@ -18,5 +18,5 @@ ...@@ -18,5 +18,5 @@
locals: { registration_token: @group.runners_token, locals: { registration_token: @group.runners_token,
type: 'group', type: 'group',
reset_token_url: reset_registration_token_group_settings_ci_cd_path, reset_token_url: reset_registration_token_group_settings_ci_cd_path,
instructions_path: runner_setup_scripts_group_settings_ci_cd_path(@group), project_path: '',
platforms_path: runner_setup_platforms_path } group_path: @group.path }
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
locals: { registration_token: @project.runners_token, locals: { registration_token: @project.runners_token,
type: 'specific', type: 'specific',
reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path, reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path,
instructions_path: runner_setup_scripts_namespace_project_settings_ci_cd_path(project_id: @project, namespace_id: @project.namespace), project_path: @project.path_with_namespace,
platforms_path: runner_setup_platforms_path } group_path: '' }
- if @project_runners.any? - if @project_runners.any?
%h4.underlined-title= _('Runners activated for this project') %h4.underlined-title= _('Runners activated for this project')
......
export const mockPlatformsObject = { export const mockGraphqlRunnerPlatforms = {
linux: { data: {
human_readable_name: 'Linux', runnerPlatforms: {
download_locations: { nodes: [
'386': {
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386', name: 'linux',
amd64: humanReadableName: 'Linux',
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64', architectures: {
arm: nodes: [
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm', {
arm64: name: 'amd64',
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64', downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64',
__typename: 'RunnerArchitecture',
},
{
name: '386',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386',
__typename: 'RunnerArchitecture',
},
{
name: 'arm',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm',
__typename: 'RunnerArchitecture',
},
{
name: 'arm64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64',
__typename: 'RunnerArchitecture',
},
],
__typename: 'RunnerArchitectureConnection',
},
__typename: 'RunnerPlatform',
},
{
name: 'osx',
humanReadableName: 'macOS',
architectures: {
nodes: [
{
name: 'amd64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64',
__typename: 'RunnerArchitecture',
},
],
__typename: 'RunnerArchitectureConnection',
},
__typename: 'RunnerPlatform',
},
{
name: 'windows',
humanReadableName: 'Windows',
architectures: {
nodes: [
{
name: 'amd64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe',
__typename: 'RunnerArchitecture',
},
{
name: '386',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe',
__typename: 'RunnerArchitecture',
},
],
__typename: 'RunnerArchitectureConnection',
},
__typename: 'RunnerPlatform',
},
{
name: 'docker',
humanReadableName: 'Docker',
architectures: null,
__typename: 'RunnerPlatform',
},
{
name: 'kubernetes',
humanReadableName: 'Kubernetes',
architectures: null,
__typename: 'RunnerPlatform',
},
],
__typename: 'RunnerPlatformConnection',
}, },
install_script_template_path: 'lib/gitlab/ci/runner_instructions/templates/linux/install.sh',
runner_executable: 'sudo gitlab-runner',
}, },
osx: { };
human_readable_name: 'macOS',
download_locations: { export const mockGraphqlInstructions = {
amd64: data: {
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64', runnerSetup: {
installInstructions:
"# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n",
registerInstructions:
'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz',
__typename: 'RunnerSetup',
}, },
install_script_template_path: 'lib/gitlab/ci/runner_instructions/templates/osx/install.sh',
runner_executable: 'sudo gitlab-runner',
}, },
windows: { };
human_readable_name: 'Windows',
download_locations: { export const mockGraphqlProjectId = {
'386': data: {
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe', project: {
amd64: id: 'gid://gitlab/Project/1',
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe',
}, },
install_script_template_path: 'lib/gitlab/ci/runner_instructions/templates/windows/install.ps1',
runner_executable: './gitlab-runner.exe',
},
docker: {
human_readable_name: 'Docker',
installation_instructions_url: 'https://docs.gitlab.com/runner/install/docker.html',
},
kubernetes: {
human_readable_name: 'Kubernetes',
installation_instructions_url: 'https://docs.gitlab.com/runner/install/kubernetes.html',
}, },
}; };
export const mockInstructions = { export const mockGraphqlGroupId = {
install: data: {
"# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n", group: null,
register: },
'sudo gitlab-runner register --url http://0.0.0.0:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz',
}; };
import axios from 'axios'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils'; import createMockApollo from 'jest/helpers/mock_apollo_helper';
import statusCodes from '~/lib/utils/http_status';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import { createStore } from '~/vue_shared/components/runner_instructions/store/'; import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql';
import * as types from '~/vue_shared/components/runner_instructions/store/mutation_types'; import getProjectId from '~/vue_shared/components/runner_instructions/graphql/queries/get_project_id.query.graphql';
import getGroupId from '~/vue_shared/components/runner_instructions/graphql/queries/get_group_id.query.graphql';
import { mockPlatformsObject, mockInstructions } from './mock_data'; import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql';
const instructionsPath = '/instructions'; import {
const platformsPath = '/platforms'; mockGraphqlRunnerPlatforms,
mockGraphqlProjectId,
mockGraphqlInstructions,
mockGraphqlGroupId,
} from './mock_data';
const projectPath = 'gitlab-org/gitlab';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('RunnerInstructions component', () => { describe('RunnerInstructions component', () => {
let wrapper; let wrapper;
let store; let fakeApollo;
let mock;
const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]'); const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]');
const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]'); const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]');
...@@ -23,31 +29,23 @@ describe('RunnerInstructions component', () => { ...@@ -23,31 +29,23 @@ describe('RunnerInstructions component', () => {
const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]'); const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]');
const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]'); const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]');
function setupStore() {
store.commit(`installRunnerPopup/${types.SET_AVAILABLE_PLATFORMS}`, mockPlatformsObject);
store.commit(`installRunnerPopup/${types.SET_AVAILABLE_PLATFORM}`, 'linux');
store.commit(`installRunnerPopup/${types.SET_ARCHITECTURE}`, '386');
store.commit(`installRunnerPopup/${types.SET_INSTRUCTIONS}`, mockInstructions);
}
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); const requestHandlers = [
[getRunnerPlatforms, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)],
mock.onGet(platformsPath).reply(statusCodes.OK, mockPlatformsObject); [getRunnerSetupInstructions, jest.fn().mockResolvedValue(mockGraphqlInstructions)],
[getProjectId, jest.fn().mockResolvedValue(mockGraphqlProjectId)],
mock.onGet('/instructions?os=linux&arch=386').reply(statusCodes.OK, mockInstructions); [getGroupId, jest.fn().mockResolvedValue(mockGraphqlGroupId)],
];
store = createStore({
instructionsPath, fakeApollo = createMockApollo(requestHandlers);
platformsPath,
wrapper = shallowMount(RunnerInstructions, {
provide: {
projectPath,
},
localVue,
apolloProvider: fakeApollo,
}); });
wrapper = shallowMount(RunnerInstructions, { store });
setupStore();
}); });
afterEach(() => { afterEach(() => {
...@@ -65,25 +63,35 @@ describe('RunnerInstructions component', () => { ...@@ -65,25 +63,35 @@ describe('RunnerInstructions component', () => {
it('should contain a number of platforms buttons', () => { it('should contain a number of platforms buttons', () => {
const buttons = findPlatformButtons(); const buttons = findPlatformButtons();
expect(buttons).toHaveLength(Object.keys(mockPlatformsObject).length); expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
}); });
it('should contain a number of dropdown items for the architecture options', () => { it('should contain a number of dropdown items for the architecture options', () => {
const dropdownItems = findArchitectureDropdownItems(); const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
expect(dropdownItems).toHaveLength( return wrapper.vm.$nextTick(() => {
Object.keys(mockPlatformsObject.linux.download_locations).length, const dropdownItems = findArchitectureDropdownItems();
);
expect(dropdownItems).toHaveLength(
mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
);
});
}); });
it('should display the binary installation instructions for a selected architecture', () => { it('should display the binary installation instructions for a selected architecture', async () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
await wrapper.vm.$nextTick();
const dropdownItem = findArchitectureDropdownItems().at(0);
dropdownItem.vm.$emit('click');
await wrapper.vm.$nextTick();
const runner = findBinaryInstructionsSection(); const runner = findBinaryInstructionsSection();
expect(runner.text()).toEqual(
expect.stringContaining(
'sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386',
),
);
expect(runner.text()).toEqual( expect(runner.text()).toEqual(
expect.stringContaining('sudo chmod +x /usr/local/bin/gitlab-runner'), expect.stringContaining('sudo chmod +x /usr/local/bin/gitlab-runner'),
); );
...@@ -100,9 +108,21 @@ describe('RunnerInstructions component', () => { ...@@ -100,9 +108,21 @@ describe('RunnerInstructions component', () => {
expect(runner.text()).toEqual(expect.stringContaining('sudo gitlab-runner start')); expect(runner.text()).toEqual(expect.stringContaining('sudo gitlab-runner start'));
}); });
it('should display the runner instructions for a selected architecture', () => { it('should display the runner register instructions for a selected architecture', async () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
await wrapper.vm.$nextTick();
const dropdownItem = findArchitectureDropdownItems().at(0);
dropdownItem.vm.$emit('click');
await wrapper.vm.$nextTick();
const runner = findRunnerInstructionsSection(); const runner = findRunnerInstructionsSection();
expect(runner.text()).toEqual(expect.stringContaining(mockInstructions.register)); expect(runner.text()).toEqual(
expect.stringContaining(mockGraphqlInstructions.data.runnerSetup.registerInstructions),
);
}); });
}); });
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import * as actions from '~/vue_shared/components/runner_instructions/store/actions';
import * as types from '~/vue_shared/components/runner_instructions/store/mutation_types';
import createState from '~/vue_shared/components/runner_instructions/store/state';
import { mockPlatformsObject, mockInstructions } from '../mock_data';
describe('Runner Instructions actions', () => {
let state;
let axiosMock;
beforeEach(() => {
state = createState();
axiosMock = new MockAdapter(axios);
});
afterEach(() => {
axiosMock.restore();
});
describe('selectPlatform', () => {
it('commits the SET_AVAILABLE_PLATFORM mutation and calls two actions', done => {
testAction(
actions.selectPlatform,
null,
state,
[{ type: types.SET_AVAILABLE_PLATFORM, payload: null }],
[{ type: 'selectArchitecture', payload: '' }, { type: 'requestPlatformsInstructions' }],
done,
);
});
});
describe('selectArchitecture', () => {
it('commits the SET_ARCHITECTURE mutation', done => {
testAction(
actions.selectArchitecture,
null,
state,
[{ type: types.SET_ARCHITECTURE, payload: null }],
[],
done,
);
});
});
describe('requestPlatformsInstructions', () => {
describe('successful request', () => {
beforeEach(() => {
state.instructionsPath = '/instructions';
state.selectedAvailablePlatform = 'linux';
state.selectedArchitecture = 'amd64';
axiosMock
.onGet(`${state.instructionsPath}?os=linux&arch=amd64`)
.reply(statusCodes.OK, mockInstructions);
});
it('commits the SET_INSTRUCTIONS mutation', done => {
testAction(
actions.requestPlatformsInstructions,
null,
state,
[{ type: types.SET_INSTRUCTIONS, payload: mockInstructions }],
[],
done,
);
});
});
describe('unsuccessful request', () => {
beforeEach(() => {
state.instructionsPath = '/instructions';
axiosMock.onGet(state.instructionsPath).reply(500);
});
it('shows an error', done => {
testAction(
actions.requestPlatformsInstructions,
null,
state,
[],
[{ type: 'toggleAlert', payload: true }],
done,
);
});
});
});
describe('requestPlatforms', () => {
describe('successful request', () => {
beforeEach(() => {
state.platformsPath = '/platforms';
state.availablePlatforms = mockPlatformsObject;
state.getSupportedArchitectures = ['linux', 'windows'];
axiosMock.onGet(state.platformsPath).reply(statusCodes.OK, mockPlatformsObject);
});
it('commits the SET_AVAILABLE_PLATFORMS mutation', done => {
testAction(
actions.requestPlatforms,
null,
state,
[
{ type: types.SET_AVAILABLE_PLATFORMS, payload: mockPlatformsObject },
{ type: types.SET_AVAILABLE_PLATFORM, payload: 'linux' },
],
[
{ type: 'selectArchitecture', payload: 'linux' },
{ type: 'requestPlatformsInstructions' },
],
done,
);
});
});
describe('unsuccessful request', () => {
beforeEach(() => {
state.platformsPath = '/instructions';
axiosMock.onGet(state.platformsPath).reply(500);
});
it('shows an error', done => {
testAction(
actions.requestPlatforms,
null,
state,
[],
[{ type: 'toggleAlert', payload: true }],
done,
);
});
});
});
describe('startInstructionsRequest', () => {
it('dispatches two actions', done => {
testAction(
actions.startInstructionsRequest,
'linux',
state,
[],
[
{ type: 'selectArchitecture', payload: 'linux' },
{ type: 'requestPlatformsInstructions' },
],
done,
);
});
});
describe('toggleAlert', () => {
it('commits the SET_SHOW_ALERT mutation', done => {
testAction(
actions.toggleAlert,
true,
state,
[{ type: types.SET_SHOW_ALERT, payload: true }],
[],
done,
);
});
});
});
import * as getters from '~/vue_shared/components/runner_instructions/store/getters';
import createState from '~/vue_shared/components/runner_instructions/store/state';
import { mockPlatformsObject, mockInstructions } from '../mock_data';
describe('Runner Instructions Store Getters', () => {
let state;
beforeEach(() => {
state = createState({
instructionsPath: '/instructions',
availablePlatforms: mockPlatformsObject,
selectedAvailablePlatform: 'linux',
selectedArchitecture: 'amd64',
instructions: mockInstructions,
});
});
describe('getSupportedArchitectures', () => {
let getSupportedArchitectures;
beforeEach(() => {
getSupportedArchitectures = getters.getSupportedArchitectures(state);
});
it('should the list of supported architectures', () => {
expect(getSupportedArchitectures).toHaveLength(
Object.keys(mockPlatformsObject.linux.download_locations).length,
);
expect(getSupportedArchitectures).toEqual(
Object.keys(mockPlatformsObject.linux.download_locations),
);
});
});
describe('hasDownloadLocationsAvailable', () => {
let hasDownloadLocationsAvailable;
beforeEach(() => {
hasDownloadLocationsAvailable = getters.hasDownloadLocationsAvailable(state);
});
it('should get the list of download locations for each architecture', () => {
expect(hasDownloadLocationsAvailable).toEqual(mockPlatformsObject.linux.download_locations);
});
});
describe('instructionsEmpty', () => {
let instructionsEmpty;
beforeEach(() => {
instructionsEmpty = getters.instructionsEmpty(state);
});
it('should return false if the instruction object is not empty', () => {
expect(instructionsEmpty).toBe(false);
});
});
describe('getDownloadLocation', () => {
let getDownloadLocation;
beforeEach(() => {
getDownloadLocation = getters.getDownloadLocation(state);
});
it('should return the download link for the selected platform and architecture', () => {
expect(getDownloadLocation).toBe(mockPlatformsObject.linux.download_locations.amd64);
});
});
});
import mutations from '~/vue_shared/components/runner_instructions/store/mutations';
import createState from '~/vue_shared/components/runner_instructions/store/state';
import { mockPlatformsObject, mockInstructions } from '../mock_data';
describe('Runner Instructions mutations', () => {
let localState;
beforeEach(() => {
localState = createState();
});
describe('SET_AVAILABLE_PLATFORMS', () => {
it('should set the availablePlatforms object', () => {
mutations.SET_AVAILABLE_PLATFORMS(localState, mockPlatformsObject);
expect(localState.availablePlatforms).toEqual(mockPlatformsObject);
});
});
describe('SET_AVAILABLE_PLATFORM', () => {
it('should set the selectedAvailablePlatform key', () => {
mutations.SET_AVAILABLE_PLATFORM(localState, 'linux');
expect(localState.selectedAvailablePlatform).toBe('linux');
});
});
describe('SET_ARCHITECTURE', () => {
it('should set the selectedArchitecture key', () => {
mutations.SET_ARCHITECTURE(localState, 'amd64');
expect(localState.selectedArchitecture).toBe('amd64');
});
});
describe('SET_INSTRUCTIONS', () => {
it('should set the instructions object', () => {
mutations.SET_INSTRUCTIONS(localState, mockInstructions);
expect(localState.instructions).toEqual(mockInstructions);
});
});
describe('SET_SHOW_ALERT', () => {
it('should set the showAlert boolean', () => {
mutations.SET_SHOW_ALERT(localState, true);
expect(localState.showAlert).toEqual(true);
});
});
});
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