Commit 61508b55 authored by Miguel Rincon's avatar Miguel Rincon

Display list of group Runners

This change adds a list of runners to the group runners page. This is a
first iteration that does not include filters and/or pagination for
results.
parent edf96966
#import "~/runner/graphql/runner_node.fragment.graphql"
query getGroupRunners($groupFullPath: ID!) {
group(fullPath: $groupFullPath) {
runners(membership: DESCENDANTS) {
nodes {
...RunnerNode
}
}
}
}
<script>
import createFlash from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import RunnerList from '../components/runner_list.vue';
import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue';
import RunnerTypeHelp from '../components/runner_type_help.vue';
import { GROUP_TYPE } from '../constants';
import { I18N_FETCH_ERROR, GROUP_TYPE } from '../constants';
import getGroupRunnersQuery from '../graphql/get_group_runners.query.graphql';
import { captureException } from '../sentry_utils';
export default {
name: 'GroupRunnersApp',
components: {
RunnerList,
RunnerManualSetupHelp,
RunnerTypeHelp,
},
......@@ -13,6 +20,61 @@ export default {
type: String,
required: true,
},
groupFullPath: {
type: String,
required: true,
},
},
data() {
return {
runners: {
items: [],
},
};
},
apollo: {
runners: {
query: getGroupRunnersQuery,
// Runners can be updated by users directly in this list.
// A "cache and network" policy prevents outdated filtered
// results.
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
variables() {
return this.variables;
},
update(data) {
const { runners } = data?.group;
return {
items: runners?.nodes || [],
};
},
error(error) {
createFlash({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
},
computed: {
variables() {
return {
groupFullPath: this.groupFullPath,
};
},
runnersLoading() {
return this.$apollo.queries.runners.loading;
},
noRunnersFound() {
return !this.runnersLoading && !this.runners.items.length;
},
},
errorCaptured(error) {
this.reportToSentry(error);
},
methods: {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
},
GROUP_TYPE,
};
......@@ -31,5 +93,12 @@ export default {
/>
</div>
</div>
<div v-if="noRunnersFound" class="gl-text-center gl-p-5">
{{ __('No runners found') }}
</div>
<template v-else>
<runner-list :runners="runners.items" :loading="runnersLoading" />
</template>
</div>
</template>
......@@ -12,7 +12,7 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
return null;
}
const { registrationToken, groupId } = el.dataset;
const { registrationToken, runnerInstallHelpPage, groupId, groupFullPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
......@@ -27,12 +27,14 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
el,
apolloProvider,
provide: {
runnerInstallHelpPage,
groupId,
},
render(h) {
return h(GroupRunnersApp, {
props: {
registrationToken,
groupFullPath,
},
});
},
......
......@@ -3,4 +3,4 @@
%h2.page-title
= s_('Runners|Group Runners')
#js-group-runners{ data: { registration_token: @group.runners_token, group_id: @group.id } }
#js-group-runners{ data: { registration_token: @group.runners_token, runner_install_help_page: 'https://docs.gitlab.com/runner/install/', group_id: @group.id, group_full_path: @group.full_path } }
......@@ -14,6 +14,7 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
let_it_be(:instance_runner) { create(:ci_runner, :instance, version: '1.0.0', revision: '123', description: 'Instance runner', ip_address: '127.0.0.1') }
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner', ip_address: '127.0.0.1') }
let_it_be(:group_runner_2) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner 2', ip_address: '127.0.0.1') }
let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project], active: false, version: '2.0.0', revision: '456', description: 'Project runner', ip_address: '127.0.0.1') }
query_path = 'runner/graphql/'
......@@ -27,14 +28,14 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
remove_repository(project)
end
before do
sign_in(admin)
enable_admin_mode!(admin)
end
describe GraphQL::Query, type: :request do
get_runners_query_name = 'get_runners.query.graphql'
before do
sign_in(admin)
enable_admin_mode!(admin)
end
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runners_query_name}")
end
......@@ -55,6 +56,11 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
describe GraphQL::Query, type: :request do
get_runner_query_name = 'get_runner.query.graphql'
before do
sign_in(admin)
enable_admin_mode!(admin)
end
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runner_query_name}")
end
......@@ -67,4 +73,26 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
expect_graphql_errors_to_be_empty
end
end
describe GraphQL::Query, type: :request do
get_group_runners_query_name = 'get_group_runners.query.graphql'
let_it_be(:group_owner) { create(:user) }
before do
group.add_owner(group_owner)
end
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_group_runners_query_name}")
end
it "#{fixtures_path}#{get_group_runners_query_name}.json" do
post_graphql(query, current_user: group_owner, variables: {
groupFullPath: group.full_path
})
expect_graphql_errors_to_be_empty
end
end
end
......@@ -78,7 +78,7 @@ describe('AdminRunnersApp', () => {
});
it('shows the runners list', () => {
expect(runnersData.data.runners.nodes).toMatchObject(findRunnerList().props('runners'));
expect(findRunnerList().props('runners')).toEqual(runnersData.data.runners.nodes);
});
it('requests the runners with no filters', () => {
......
......@@ -56,7 +56,7 @@ describe('RunnerList', () => {
});
it('Displays a list of runners', () => {
expect(findRows()).toHaveLength(3);
expect(findRows()).toHaveLength(4);
expect(findSkeletonLoader().exists()).toBe(false);
});
......
import { shallowMount } from '@vue/test-utils';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue';
import RunnerTypeHelp from '~/runner/components/runner_type_help.vue';
import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql';
import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
import { groupRunnersData } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
const mockGroupFullPath = 'group1';
const mockRegistrationToken = 'AABBCC';
describe('GroupRunnersApp', () => {
let wrapper;
let mockGroupRunnersQuery;
const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp);
const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp);
const findRunnerList = () => wrapper.findComponent(RunnerList);
const createComponent = ({ mountFn = shallowMount } = {}) => {
const handlers = [[getGroupRunnersQuery, mockGroupRunnersQuery]];
wrapper = mountFn(GroupRunnersApp, {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,
groupFullPath: mockGroupFullPath,
},
});
};
beforeEach(() => {
beforeEach(async () => {
mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersData);
createComponent();
await waitForPromises();
});
it('shows the runner type help', () => {
......@@ -31,4 +51,8 @@ describe('GroupRunnersApp', () => {
expect(findRunnerManualSetupHelp().exists()).toBe(true);
expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken);
});
it('shows the runners list', () => {
expect(findRunnerList().props('runners')).toEqual(groupRunnersData.data.group.runners.nodes);
});
});
const runnerFixture = (filename) => getJSONFixture(`graphql/runner/${filename}`);
// Fixtures generated by: spec/frontend/fixtures/runner.rb
export const runnersData = getJSONFixture('graphql/runner/get_runners.query.graphql.json');
export const runnersDataPaginated = getJSONFixture(
'graphql/runner/get_runners.query.graphql.paginated.json',
);
export const runnerData = getJSONFixture('graphql/runner/get_runner.query.graphql.json');
// Admin queries
export const runnersData = runnerFixture('get_runners.query.graphql.json');
export const runnersDataPaginated = runnerFixture('get_runners.query.graphql.paginated.json');
export const runnerData = runnerFixture('get_runner.query.graphql.json');
// Group queries
export const groupRunnersData = runnerFixture('get_group_runners.query.graphql.json');
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