Commit 8c69151e authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '346061-add-additional-runner-statuses-to-single-stat-summary' into 'master'

Add offline, stale count to runner admins section

See merge request gitlab-org/gitlab!77871
parents 831bad50 1351c794
......@@ -9,7 +9,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerName from '../components/runner_name.vue';
import RunnerOnlineStat from '../components/stat/runner_online_stat.vue';
import RunnerStats from '../components/stat/runner_stats.vue';
import RunnerPagination from '../components/runner_pagination.vue';
import RunnerTypeTabs from '../components/runner_type_tabs.vue';
......@@ -20,6 +20,9 @@ import {
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
I18N_FETCH_ERROR,
} from '../constants';
import getRunnersQuery from '../graphql/get_runners.query.graphql';
......@@ -51,7 +54,7 @@ export default {
RunnerFilteredSearchBar,
RunnerList,
RunnerName,
RunnerOnlineStat,
RunnerStats,
RunnerPagination,
RunnerTypeTabs,
},
......@@ -60,10 +63,6 @@ export default {
type: String,
required: true,
},
activeRunnersCount: {
type: String,
required: true,
},
},
data() {
return {
......@@ -130,6 +129,30 @@ export default {
};
},
},
onlineRunnersTotal: {
...runnersCountSmartQuery,
variables() {
return {
status: STATUS_ONLINE,
};
},
},
offlineRunnersTotal: {
...runnersCountSmartQuery,
variables() {
return {
status: STATUS_OFFLINE,
};
},
},
staleRunnersTotal: {
...runnersCountSmartQuery,
variables() {
return {
status: STATUS_STALE,
};
},
},
},
computed: {
variables() {
......@@ -205,7 +228,11 @@ export default {
</script>
<template>
<div>
<runner-online-stat class="gl-py-6 gl-px-5" :value="activeRunnersCount" />
<runner-stats
:online-runners-count="onlineRunnersTotal"
:offline-runners-count="offlineRunnersTotal"
:stale-runners-count="staleRunnersTotal"
/>
<div
class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
......
......@@ -25,9 +25,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return null;
}
// TODO `activeRunnersCount` should be implemented using a GraphQL API
// https://gitlab.com/gitlab-org/gitlab/-/issues/333806
const { runnerInstallHelpPage, registrationToken, activeRunnersCount } = el.dataset;
const { runnerInstallHelpPage, registrationToken } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
......@@ -43,10 +41,6 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return h(AdminRunnersApp, {
props: {
registrationToken,
// Runner counts are returned as formatted
// strings, we do not use `parseInt`.
activeRunnersCount,
},
});
},
......
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
export default {
components: {
GlSingleStat,
},
};
</script>
<template>
<gl-single-stat
v-bind="$attrs"
variant="success"
:title="s__('Runners|Online Runners')"
:meta-text="s__('Runners|online')"
/>
</template>
<script>
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
import RunnerStatusStat from './runner_status_stat.vue';
export default {
components: {
RunnerStatusStat,
},
props: {
onlineRunnersCount: {
type: Number,
required: false,
default: null,
},
offlineRunnersCount: {
type: Number,
required: false,
default: null,
},
staleRunnersCount: {
type: Number,
required: false,
default: null,
},
},
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
};
</script>
<template>
<div class="gl-display-flex gl-py-6">
<runner-status-stat
class="gl-px-5"
:status="$options.STATUS_ONLINE"
:value="onlineRunnersCount"
/>
<runner-status-stat
class="gl-px-5"
:status="$options.STATUS_OFFLINE"
:value="offlineRunnersCount"
/>
<runner-status-stat
class="gl-px-5"
:status="$options.STATUS_STALE"
:value="staleRunnersCount"
/>
</div>
</template>
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { s__, formatNumber } from '~/locale';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
export default {
components: {
GlSingleStat,
},
props: {
value: {
type: Number,
required: false,
default: null,
},
status: {
type: String,
required: true,
},
},
computed: {
formattedValue() {
if (typeof this.value === 'number') {
return formatNumber(this.value);
}
return '-';
},
stat() {
switch (this.status) {
case STATUS_ONLINE:
return {
variant: 'success',
title: s__('Runners|Online runners'),
metaText: s__('Runners|online'),
};
case STATUS_OFFLINE:
return {
variant: 'muted',
title: s__('Runners|Offline runners'),
metaText: s__('Runners|offline'),
};
case STATUS_STALE:
return {
variant: 'warning',
title: s__('Runners|Stale runners'),
metaText: s__('Runners|stale'),
};
default:
return {
title: s__('Runners|Runners'),
};
}
},
},
};
</script>
<template>
<gl-single-stat
v-if="stat"
:value="formattedValue"
:variant="stat.variant"
:title="stat.title"
:meta-text="stat.metaText"
/>
</template>
......@@ -13,7 +13,7 @@ query getGroupRunners(
$sort: CiRunnerSort
) {
group(fullPath: $groupFullPath) {
id
id # Apollo required
runners(
membership: DESCENDANTS
before: $before
......
query getGroupRunnersCount(
$groupFullPath: ID!
$status: CiRunnerStatus
$type: CiRunnerType
$tagList: [String!]
$search: String
) {
group(fullPath: $groupFullPath) {
id # Apollo required
runners(
membership: DESCENDANTS
status: $status
type: $type
tagList: $tagList
search: $search
) {
count
}
}
}
......@@ -9,7 +9,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerName from '../components/runner_name.vue';
import RunnerOnlineStat from '../components/stat/runner_online_stat.vue';
import RunnerStats from '../components/stat/runner_stats.vue';
import RunnerPagination from '../components/runner_pagination.vue';
import RunnerTypeTabs from '../components/runner_type_tabs.vue';
......@@ -19,8 +19,12 @@ import {
GROUP_FILTERED_SEARCH_NAMESPACE,
GROUP_TYPE,
GROUP_RUNNER_COUNT_LIMIT,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
} from '../constants';
import getGroupRunnersQuery from '../graphql/get_group_runners.query.graphql';
import getGroupRunnersCountQuery from '../graphql/get_group_runners_count.query.graphql';
import {
fromUrlQueryToSearch,
fromSearchToUrl,
......@@ -28,6 +32,17 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
const runnersCountSmartQuery = {
query: getGroupRunnersCountQuery,
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
update(data) {
return data?.group?.runners?.count;
},
error(error) {
this.reportToSentry(error);
},
};
export default {
name: 'GroupRunnersApp',
components: {
......@@ -36,7 +51,7 @@ export default {
RunnerFilteredSearchBar,
RunnerList,
RunnerName,
RunnerOnlineStat,
RunnerStats,
RunnerPagination,
RunnerTypeTabs,
},
......@@ -89,6 +104,33 @@ export default {
this.reportToSentry(error);
},
},
onlineRunnersTotal: {
...runnersCountSmartQuery,
variables() {
return {
groupFullPath: this.groupFullPath,
status: STATUS_ONLINE,
};
},
},
offlineRunnersTotal: {
...runnersCountSmartQuery,
variables() {
return {
groupFullPath: this.groupFullPath,
status: STATUS_OFFLINE,
};
},
},
staleRunnersTotal: {
...runnersCountSmartQuery,
variables() {
return {
groupFullPath: this.groupFullPath,
status: STATUS_STALE,
};
},
},
},
computed: {
variables() {
......@@ -147,7 +189,11 @@ export default {
<template>
<div>
<runner-online-stat class="gl-py-6 gl-px-5" :value="groupRunnersCount" />
<runner-stats
:online-runners-count="onlineRunnersTotal"
:offline-runners-count="offlineRunnersTotal"
:stale-runners-count="staleRunnersTotal"
/>
<div class="gl-display-flex gl-align-items-center">
<runner-type-tabs
......
......@@ -65,10 +65,7 @@ module Ci
# Runner install help page is external, located at
# https://gitlab.com/gitlab-org/gitlab-runner
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
registration_token: Gitlab::CurrentSettings.runners_registration_token,
# Runner counts are returned as formatted strings
active_runners_count: Ci::Runner.online.count.to_s
registration_token: Gitlab::CurrentSettings.runners_registration_token
}
end
......
......@@ -30741,10 +30741,13 @@ msgstr ""
msgid "Runners|Offline"
msgstr ""
msgid "Runners|Offline runners"
msgstr ""
msgid "Runners|Online"
msgstr ""
msgid "Runners|Online Runners"
msgid "Runners|Online runners"
msgstr ""
msgid "Runners|Paused"
......@@ -30840,6 +30843,9 @@ msgstr ""
msgid "Runners|Stale"
msgstr ""
msgid "Runners|Stale runners"
msgstr ""
msgid "Runners|Status"
msgstr ""
......
......@@ -21,12 +21,16 @@ RSpec.describe "Admin Runners" do
context "when there are runners" do
it 'has all necessary texts' do
create(:ci_runner, :instance, contacted_at: Time.now)
create(:ci_runner, :instance, created_at: 1.year.ago, contacted_at: Time.now)
create(:ci_runner, :instance, created_at: 1.year.ago, contacted_at: 1.week.ago)
create(:ci_runner, :instance, created_at: 1.year.ago, contacted_at: 1.year.ago)
visit admin_runners_path
expect(page).to have_text "Register an instance runner"
expect(page).to have_text "Online Runners 1"
expect(page).to have_text "Online runners 1"
expect(page).to have_text "Offline runners 2"
expect(page).to have_text "Stale runners 1"
end
it 'with an instance runner shows an instance badge' do
......@@ -387,7 +391,11 @@ RSpec.describe "Admin Runners" do
it 'has all necessary texts including no runner message' do
expect(page).to have_text "Register an instance runner"
expect(page).to have_text "Online Runners 0"
expect(page).to have_text "Online runners 0"
expect(page).to have_text "Offline runners 0"
expect(page).to have_text "Stale runners 0"
expect(page).to have_text 'No runners found'
end
......
......@@ -24,99 +24,109 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
remove_repository(project)
end
describe GraphQL::Query, type: :request do
get_runners_query_name = 'get_runners.query.graphql'
describe do
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
describe GraphQL::Query, type: :request do
get_runners_query_name = 'get_runners.query.graphql'
it "#{fixtures_path}#{get_runners_query_name}.json" do
post_graphql(query, current_user: admin, variables: {})
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runners_query_name}")
end
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{get_runners_query_name}.json" do
post_graphql(query, current_user: admin, variables: {})
it "#{fixtures_path}#{get_runners_query_name}.paginated.json" do
post_graphql(query, current_user: admin, variables: { first: 2 })
expect_graphql_errors_to_be_empty
end
expect_graphql_errors_to_be_empty
end
end
describe GraphQL::Query, type: :request do
get_runners_count_query_name = 'get_runners_count.query.graphql'
it "#{fixtures_path}#{get_runners_query_name}.paginated.json" do
post_graphql(query, current_user: admin, variables: { first: 2 })
before do
sign_in(admin)
enable_admin_mode!(admin)
end
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runners_count_query_name}")
expect_graphql_errors_to_be_empty
end
end
it "#{fixtures_path}#{get_runners_count_query_name}.json" do
post_graphql(query, current_user: admin, variables: {})
describe GraphQL::Query, type: :request do
get_runners_count_query_name = 'get_runners_count.query.graphql'
expect_graphql_errors_to_be_empty
end
end
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runners_count_query_name}")
end
describe GraphQL::Query, type: :request do
get_runner_query_name = 'get_runner.query.graphql'
it "#{fixtures_path}#{get_runners_count_query_name}.json" do
post_graphql(query, current_user: admin, variables: {})
before do
sign_in(admin)
enable_admin_mode!(admin)
expect_graphql_errors_to_be_empty
end
end
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runner_query_name}")
end
describe GraphQL::Query, type: :request do
get_runner_query_name = 'get_runner.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runner_query_name}")
end
it "#{fixtures_path}#{get_runner_query_name}.json" do
post_graphql(query, current_user: admin, variables: {
id: instance_runner.to_global_id.to_s
})
it "#{fixtures_path}#{get_runner_query_name}.json" do
post_graphql(query, current_user: admin, variables: {
id: instance_runner.to_global_id.to_s
})
expect_graphql_errors_to_be_empty
expect_graphql_errors_to_be_empty
end
end
end
describe GraphQL::Query, type: :request do
get_group_runners_query_name = 'get_group_runners.query.graphql'
describe do
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
describe GraphQL::Query, type: :request do
get_group_runners_query_name = 'get_group_runners.query.graphql'
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
})
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
expect_graphql_errors_to_be_empty
it "#{fixtures_path}#{get_group_runners_query_name}.paginated.json" do
post_graphql(query, current_user: group_owner, variables: {
groupFullPath: group.full_path,
first: 1
})
expect_graphql_errors_to_be_empty
end
end
it "#{fixtures_path}#{get_group_runners_query_name}.paginated.json" do
post_graphql(query, current_user: group_owner, variables: {
groupFullPath: group.full_path,
first: 1
})
describe GraphQL::Query, type: :request do
get_group_runners_count_query_name = 'get_group_runners_count.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_group_runners_count_query_name}")
end
it "#{fixtures_path}#{get_group_runners_count_query_name}.json" do
post_graphql(query, current_user: group_owner, variables: {
groupFullPath: group.full_path
})
expect_graphql_errors_to_be_empty
expect_graphql_errors_to_be_empty
end
end
end
end
......@@ -13,6 +13,7 @@ import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
......@@ -37,7 +38,6 @@ import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered
import { runnersData, runnersCountData, runnersDataPaginated } from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
const mockActiveRunnersCount = '2';
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
......@@ -54,6 +54,7 @@ describe('AdminRunnersApp', () => {
let mockRunnersQuery;
let mockRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
......@@ -70,15 +71,16 @@ describe('AdminRunnersApp', () => {
[getRunnersCountQuery, mockRunnersCountQuery],
];
wrapper = mountFn(AdminRunnersApp, {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,
activeRunnersCount: mockActiveRunnersCount,
...props,
},
});
wrapper = extendedWrapper(
mountFn(AdminRunnersApp, {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,
...props,
},
}),
);
};
beforeEach(async () => {
......@@ -95,6 +97,18 @@ describe('AdminRunnersApp', () => {
wrapper.destroy();
});
it('shows total runner counts', async () => {
createComponent({ mountFn: mount });
await waitForPromises();
const stats = findRunnerStats().text();
expect(stats).toMatch('Online runners 4');
expect(stats).toMatch('Offline runners 4');
expect(stats).toMatch('Stale runners 4');
});
it('shows the runner tabs with a runner count for each type', async () => {
mockRunnersCountQuery.mockImplementation(({ type }) => {
let count;
......@@ -198,12 +212,6 @@ describe('AdminRunnersApp', () => {
]);
});
it('shows the active runner count', () => {
createComponent({ mountFn: mount });
expect(wrapper.text()).toMatch(new RegExp(`Online Runners ${mockActiveRunnersCount}`));
});
describe('when a filter is preselected', () => {
beforeEach(async () => {
setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`);
......
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount, mount } from '@vue/test-utils';
import RunnerOnlineBadge from '~/runner/components/stat/runner_online_stat.vue';
describe('RunnerOnlineBadge', () => {
let wrapper;
const findSingleStat = () => wrapper.findComponent(GlSingleStat);
const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
wrapper = mountFn(RunnerOnlineBadge, {
propsData: {
value: '99',
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('Uses a success appearance', () => {
createComponent({}, shallowMount);
expect(findSingleStat().props('variant')).toBe('success');
});
it('Renders a value', () => {
createComponent({}, mount);
expect(wrapper.text()).toMatch(new RegExp(`Online Runners 99\\s+online`));
});
});
import { shallowMount, mount } from '@vue/test-utils';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
describe('RunnerStats', () => {
let wrapper;
const findRunnerStatusStatAt = (i) => wrapper.findAllComponents(RunnerStatusStat).at(i);
const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
wrapper = mountFn(RunnerStats, {
propsData: {
onlineRunnersCount: 3,
offlineRunnersCount: 2,
staleRunnersCount: 1,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('Displays all the stats', () => {
createComponent({ mountFn: mount });
const stats = wrapper.text();
expect(stats).toMatch('Online runners 3');
expect(stats).toMatch('Offline runners 2');
expect(stats).toMatch('Stale runners 1');
});
it.each`
i | status
${0} | ${STATUS_ONLINE}
${1} | ${STATUS_OFFLINE}
${2} | ${STATUS_STALE}
`('Displays status types at index $i', ({ i, status }) => {
createComponent();
expect(findRunnerStatusStatAt(i).props('status')).toBe(status);
});
});
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount, mount } from '@vue/test-utils';
import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
describe('RunnerStatusStat', () => {
let wrapper;
const findSingleStat = () => wrapper.findComponent(GlSingleStat);
const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
wrapper = mountFn(RunnerStatusStat, {
propsData: {
status: STATUS_ONLINE,
value: 99,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe.each`
status | variant | title | badge
${STATUS_ONLINE} | ${'success'} | ${'Online runners'} | ${'online'}
${STATUS_OFFLINE} | ${'muted'} | ${'Offline runners'} | ${'offline'}
${STATUS_STALE} | ${'warning'} | ${'Stale runners'} | ${'stale'}
`('Renders a stat for status "$status"', ({ status, variant, title, badge }) => {
beforeEach(() => {
createComponent({ props: { status } }, mount);
});
it('Renders text', () => {
expect(wrapper.text()).toMatch(new RegExp(`${title} 99\\s+${badge}`));
});
it(`Uses variant ${variant}`, () => {
expect(findSingleStat().props('variant')).toBe(variant);
});
});
it('Formats stat number', () => {
createComponent({ props: { value: 1000 } }, mount);
expect(wrapper.text()).toMatch('Online runners 1,000');
});
it('Shows a null result', () => {
createComponent({ props: { value: null } }, mount);
expect(wrapper.text()).toMatch('Online runners -');
});
it('Shows an undefined result', () => {
createComponent({ props: { value: undefined } }, mount);
expect(wrapper.text()).toMatch('Online runners -');
});
it('Shows result for an unknown status', () => {
createComponent({ props: { status: 'UNKNOWN' } }, mount);
expect(wrapper.text()).toMatch('Runners 99');
});
});
......@@ -12,6 +12,7 @@ import { updateHistory } from '~/lib/utils/url_utility';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
......@@ -26,10 +27,11 @@ import {
RUNNER_PAGE_SIZE,
} from '~/runner/constants';
import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql';
import getGroupRunnersCountQuery from '~/runner/graphql/get_group_runners_count.query.graphql';
import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
import { captureException } from '~/runner/sentry_utils';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { groupRunnersData, groupRunnersDataPaginated } from '../mock_data';
import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
......@@ -48,7 +50,9 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('GroupRunnersApp', () => {
let wrapper;
let mockGroupRunnersQuery;
let mockGroupRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerList = () => wrapper.findComponent(RunnerList);
const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination));
......@@ -59,7 +63,10 @@ describe('GroupRunnersApp', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
const handlers = [[getGroupRunnersQuery, mockGroupRunnersQuery]];
const handlers = [
[getGroupRunnersQuery, mockGroupRunnersQuery],
[getGroupRunnersCountQuery, mockGroupRunnersCountQuery],
];
wrapper = mountFn(GroupRunnersApp, {
localVue,
......@@ -77,11 +84,24 @@ describe('GroupRunnersApp', () => {
setWindowLocation(`/groups/${mockGroupFullPath}/-/runners`);
mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersData);
mockGroupRunnersCountQuery = jest.fn().mockResolvedValue(groupRunnersCountData);
createComponent();
await waitForPromises();
});
it('shows total runner counts', async () => {
createComponent({ mountFn: mount });
await waitForPromises();
const stats = findRunnerStats().text();
expect(stats).toMatch('Online runners 2');
expect(stats).toMatch('Offline runners 2');
expect(stats).toMatch('Stale runners 2');
});
it('shows the runner setup instructions', () => {
expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
expect(findRegistrationDropdown().props('type')).toBe(GROUP_TYPE);
......@@ -129,28 +149,6 @@ describe('GroupRunnersApp', () => {
);
});
describe('shows the active runner count', () => {
const expectedOnlineCount = (count) => new RegExp(`Online Runners ${count}`);
it('with a regular value', () => {
createComponent({ mountFn: mount });
expect(wrapper.text()).toMatch(expectedOnlineCount(mockGroupRunnersLimitedCount));
});
it('at the limit', () => {
createComponent({ props: { groupRunnersLimitedCount: 1000 }, mountFn: mount });
expect(wrapper.text()).toMatch(expectedOnlineCount('1,000'));
});
it('over the limit', () => {
createComponent({ props: { groupRunnersLimitedCount: 1001 }, mountFn: mount });
expect(wrapper.text()).toMatch(expectedOnlineCount('1,000\\+'));
});
});
describe('when a filter is preselected', () => {
beforeEach(async () => {
setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}`);
......
......@@ -8,6 +8,7 @@ import runnerData from 'test_fixtures/graphql/runner/get_runner.query.graphql.js
// Group queries
import groupRunnersData from 'test_fixtures/graphql/runner/get_group_runners.query.graphql.json';
import groupRunnersCountData from 'test_fixtures/graphql/runner/get_group_runners_count.query.graphql.json';
import groupRunnersDataPaginated from 'test_fixtures/graphql/runner/get_group_runners.query.graphql.paginated.json';
export {
......@@ -16,5 +17,6 @@ export {
runnersDataPaginated,
runnersData,
groupRunnersData,
groupRunnersCountData,
groupRunnersDataPaginated,
};
......@@ -79,8 +79,7 @@ RSpec.describe Ci::RunnersHelper do
it 'returns the data in format' do
expect(helper.admin_runners_data_attributes).to eq({
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
registration_token: Gitlab::CurrentSettings.runners_registration_token,
active_runners_count: '0'
registration_token: Gitlab::CurrentSettings.runners_registration_token
})
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