Commit 965bb0d6 authored by Jacques Erasmus's avatar Jacques Erasmus

Merge branch '330969-update-admin-runner-spec' into 'master'

Update admin runners spec

See merge request gitlab-org/gitlab!65738
parents 7d8e54ba b2040ca9
<script>
import { cloneDeep } from 'lodash';
import { __, s__ } from '~/locale';
import { formatNumber, sprintf, __, s__ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
......@@ -58,6 +58,10 @@ export default {
type: String,
required: true,
},
activeRunnersCount: {
type: Number,
required: true,
},
},
data() {
// filtered_search_bar_root.vue may mutate the inital
......@@ -119,6 +123,11 @@ export default {
},
];
},
activeRunnersMessage() {
return sprintf(__('Runners currently online: %{active_runners_count}'), {
active_runners_count: formatNumber(this.activeRunnersCount),
});
},
},
methods: {
onFilter(filters) {
......@@ -144,16 +153,20 @@ export default {
};
</script>
<template>
<filtered-search
v-bind="$attrs"
:namespace="namespace"
recent-searches-storage-key="runners-search"
:sort-options="$options.sortOptions"
:initial-filter-value="initialFilterValue"
:initial-sort-by="initialSortBy"
:tokens="searchTokens"
:search-input-placeholder="__('Search or filter results...')"
@onFilter="onFilter"
@onSort="onSort"
/>
<div>
<filtered-search
v-bind="$attrs"
:namespace="namespace"
recent-searches-storage-key="runners-search"
:sort-options="$options.sortOptions"
:initial-filter-value="initialFilterValue"
:initial-sort-by="initialSortBy"
:tokens="searchTokens"
:search-input-placeholder="__('Search or filter results...')"
data-testid="runners-filtered-search"
@onFilter="onFilter"
@onSort="onSort"
/>
<div class="gl-text-right" data-testid="active-runners-message">{{ activeRunnersMessage }}</div>
</div>
</template>
<script>
import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { formatNumber, sprintf, __, s__ } from '~/locale';
import { formatNumber, __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { RUNNER_JOB_COUNT_LIMIT } from '../constants';
import RunnerActionsCell from './cells/runner_actions_cell.vue';
......@@ -52,17 +52,6 @@ export default {
type: Array,
required: true,
},
activeRunnersCount: {
type: Number,
required: true,
},
},
computed: {
activeRunnersMessage() {
return sprintf(__('Runners currently online: %{active_runners_count}'), {
active_runners_count: formatNumber(this.activeRunnersCount),
});
},
},
methods: {
formatProjectCount(projectCount) {
......@@ -101,12 +90,12 @@ export default {
</script>
<template>
<div>
<div class="gl-text-right" data-testid="active-runners-message">{{ activeRunnersMessage }}</div>
<gl-table
:busy="loading"
:items="runners"
:fields="$options.fields"
:tbody-tr-attr="runnerTrAttr"
data-testid="runner-list"
stacked="md"
fixed
>
......
......@@ -12,7 +12,8 @@ export const initRunnerList = (selector = '#js-runner-list') => {
return null;
}
// TODO `activeRunnersCount` should be implemented using a GraphQL API.
// TODO `activeRunnersCount` should be implemented using a GraphQL API
// https://gitlab.com/gitlab-org/gitlab/-/issues/333806
const { activeRunnersCount, registrationToken, runnerInstallHelpPage } = el.dataset;
const apolloProvider = new VueApollo({
......
......@@ -116,17 +116,17 @@ export default {
</div>
</div>
<runner-filtered-search-bar v-model="search" namespace="admin_runners" />
<runner-filtered-search-bar
v-model="search"
namespace="admin_runners"
:active-runners-count="activeRunnersCount"
/>
<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"
:active-runners-count="activeRunnersCount"
/>
<runner-list :runners="runners.items" :loading="runnersLoading" />
<runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" />
</template>
</div>
......
......@@ -4,8 +4,6 @@ require 'spec_helper'
RSpec.describe "Admin Runners" do
include StubENV
include FilteredSearchHelpers
include SortingHelper
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
......@@ -14,31 +12,68 @@ RSpec.describe "Admin Runners" do
gitlab_enable_admin_mode_sign_in(admin)
end
describe "Runners page" do
let(:pipeline) { create(:ci_pipeline) }
before do
stub_feature_flags(runner_list_view_vue_ui: false)
end
describe "Runners page", :js do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, namespace: namespace, creator: user) }
context "when there are runners" do
it 'has all necessary texts' do
runner = create(:ci_runner, contacted_at: Time.now)
create(:ci_build, pipeline: pipeline, runner_id: runner.id)
create(:ci_runner, :instance, contacted_at: Time.now)
visit admin_runners_path
expect(page).to have_text "Set up a shared runner manually"
expect(page).to have_text "Runners currently online: 1"
end
describe 'search', :js do
it 'with an instance runner shows an instance badge and no project count' do
runner = create(:ci_runner, :instance)
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'shared'
expect(page).to have_text 'n/a'
end
end
it 'with a group runner shows a group badge and no project count' do
runner = create(:ci_runner, :group, groups: [group])
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'group'
expect(page).to have_text 'n/a'
end
end
it 'with a project runner shows a project badge and project count' do
runner = create(:ci_runner, :project, projects: [project])
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'specific'
expect(page).to have_text '1'
end
end
describe 'search' do
before do
create(:ci_runner, description: 'runner-foo')
create(:ci_runner, description: 'runner-bar')
create(:ci_runner, :instance, description: 'runner-foo')
create(:ci_runner, :instance, description: 'runner-bar')
visit admin_runners_path
end
it 'shows runners' do
expect(page).to have_content("runner-foo")
expect(page).to have_content("runner-bar")
end
it 'shows correct runner when description matches' do
input_filtered_search_keys('runner-foo')
......@@ -53,28 +88,29 @@ RSpec.describe "Admin Runners" do
end
end
describe 'filter by status', :js do
describe 'filter by status' do
it 'shows correct runner when status matches' do
create(:ci_runner, description: 'runner-active', active: true)
create(:ci_runner, description: 'runner-paused', active: false)
create(:ci_runner, :instance, description: 'runner-active', active: true)
create(:ci_runner, :instance, description: 'runner-paused', active: false)
visit admin_runners_path
expect(page).to have_content 'runner-active'
expect(page).to have_content 'runner-paused'
input_filtered_search_keys('status:=active')
input_filtered_search_filter_is_only('Status', 'Active')
expect(page).to have_content 'runner-active'
expect(page).not_to have_content 'runner-paused'
end
it 'shows no runner when status does not match' do
create(:ci_runner, :online, description: 'runner-active', active: true)
create(:ci_runner, :online, description: 'runner-paused', active: false)
create(:ci_runner, :instance, description: 'runner-active', active: true)
create(:ci_runner, :instance, description: 'runner-paused', active: false)
visit admin_runners_path
input_filtered_search_keys('status:=offline')
input_filtered_search_filter_is_only('Status', 'Online')
expect(page).not_to have_content 'runner-active'
expect(page).not_to have_content 'runner-paused'
......@@ -83,46 +119,48 @@ RSpec.describe "Admin Runners" do
end
it 'shows correct runner when status is selected and search term is entered' do
create(:ci_runner, description: 'runner-a-1', active: true)
create(:ci_runner, description: 'runner-a-2', active: false)
create(:ci_runner, description: 'runner-b-1', active: true)
create(:ci_runner, :instance, description: 'runner-a-1', active: true)
create(:ci_runner, :instance, description: 'runner-a-2', active: false)
create(:ci_runner, :instance, description: 'runner-b-1', active: true)
visit admin_runners_path
input_filtered_search_keys('status:=active')
input_filtered_search_filter_is_only('Status', 'Active')
expect(page).to have_content 'runner-a-1'
expect(page).to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
input_filtered_search_keys('status:=active runner-a')
input_filtered_search_keys('runner-a')
expect(page).to have_content 'runner-a-1'
expect(page).not_to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
end
end
describe 'filter by type', :js do
it 'shows correct runner when type matches' do
create :ci_runner, :project, description: 'runner-project'
create :ci_runner, :group, description: 'runner-group'
describe 'filter by type' do
before do
create(:ci_runner, :project, description: 'runner-project', projects: [project])
create(:ci_runner, :group, description: 'runner-group', groups: [group])
end
it 'shows correct runner when type matches' do
visit admin_runners_path
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-group'
input_filtered_search_keys('type:=project_type')
input_filtered_search_filter_is_only('Type', 'project')
expect(page).to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
end
it 'shows no runner when type does not match' do
create :ci_runner, :project, description: 'runner-project'
create :ci_runner, :group, description: 'runner-group'
visit admin_runners_path
input_filtered_search_keys('type:=instance_type')
input_filtered_search_filter_is_only('Type', 'instance')
expect(page).not_to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
......@@ -131,95 +169,93 @@ RSpec.describe "Admin Runners" do
end
it 'shows correct runner when type is selected and search term is entered' do
create :ci_runner, :project, description: 'runner-a-1'
create :ci_runner, :instance, description: 'runner-a-2'
create :ci_runner, :project, description: 'runner-b-1'
create(:ci_runner, :project, description: 'runner-2-project', projects: [project])
visit admin_runners_path
input_filtered_search_keys('type:=project_type')
expect(page).to have_content 'runner-a-1'
expect(page).to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
input_filtered_search_filter_is_only('Type', 'project')
input_filtered_search_keys('type:=project_type runner-a')
expect(page).to have_content 'runner-a-1'
expect(page).not_to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-2-project'
expect(page).not_to have_content 'runner-group'
input_filtered_search_keys('runner-project')
expect(page).to have_content 'runner-project'
expect(page).not_to have_content 'runner-2-project'
expect(page).not_to have_content 'runner-group'
end
end
describe 'filter by tag', :js do
it 'shows correct runner when tag matches' do
create :ci_runner, description: 'runner-blue', tag_list: ['blue']
create :ci_runner, description: 'runner-red', tag_list: ['red']
describe 'filter by tag' do
before do
create(:ci_runner, :instance, description: 'runner-blue', tag_list: ['blue'])
create(:ci_runner, :instance, description: 'runner-red', tag_list: ['red'])
end
it 'shows correct runner when tag matches' do
visit admin_runners_path
expect(page).to have_content 'runner-blue'
expect(page).to have_content 'runner-red'
input_filtered_search_keys('tag:=blue')
input_filtered_search_filter_is_only('Tags', 'blue')
expect(page).to have_content 'runner-blue'
expect(page).not_to have_content 'runner-red'
end
it 'shows no runner when tag does not match' do
create :ci_runner, description: 'runner-blue', tag_list: ['blue']
create :ci_runner, description: 'runner-red', tag_list: ['blue']
visit admin_runners_path
input_filtered_search_keys('tag:=red')
input_filtered_search_filter_is_only('Tags', 'green')
expect(page).not_to have_content 'runner-blue'
expect(page).not_to have_content 'runner-blue'
expect(page).to have_text 'No runners found'
end
it 'shows correct runner when tag is selected and search term is entered' do
create :ci_runner, description: 'runner-a-1', tag_list: ['blue']
create :ci_runner, description: 'runner-a-2', tag_list: ['red']
create :ci_runner, description: 'runner-b-1', tag_list: ['blue']
create(:ci_runner, :instance, description: 'runner-2-blue', tag_list: ['blue'])
visit admin_runners_path
input_filtered_search_keys('tag:=blue')
input_filtered_search_filter_is_only('Tags', 'blue')
expect(page).to have_content 'runner-a-1'
expect(page).to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
expect(page).to have_content 'runner-blue'
expect(page).to have_content 'runner-2-blue'
expect(page).not_to have_content 'runner-red'
input_filtered_search_keys('tag:=blue runner-a')
input_filtered_search_keys('runner-2-blue')
expect(page).to have_content 'runner-a-1'
expect(page).not_to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
expect(page).to have_content 'runner-2-blue'
expect(page).not_to have_content 'runner-blue'
expect(page).not_to have_content 'runner-red'
end
end
it 'sorts by last contact date', :js do
create(:ci_runner, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
create(:ci_runner, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
it 'sorts by last contact date' do
create(:ci_runner, :instance, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
create(:ci_runner, :instance, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
visit admin_runners_path
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do
within '[data-testid="runner-list"] tbody tr:nth-child(1)' do
expect(page).to have_content 'runner-2'
end
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do
within '[data-testid="runner-list"] tbody tr:nth-child(2)' do
expect(page).to have_content 'runner-1'
end
sorting_by 'Last Contact'
click_on 'Created date' # Open "sort by" dropdown
click_on 'Last contact'
click_on 'Sort direction: Descending'
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do
within '[data-testid="runner-list"] tbody tr:nth-child(1)' do
expect(page).to have_content 'runner-1'
end
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do
within '[data-testid="runner-list"] tbody tr:nth-child(2)' do
expect(page).to have_content 'runner-2'
end
end
......@@ -237,47 +273,6 @@ RSpec.describe "Admin Runners" do
end
end
context 'group runner' do
let(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'shows the label and does not show the project count' do
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'group'
expect(page).to have_text 'n/a'
end
end
end
context 'shared runner' do
it 'shows the label and does not show the project count' do
runner = create(:ci_runner, :instance)
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'shared'
expect(page).to have_text 'n/a'
end
end
end
context 'specific runner' do
it 'shows the label and the project count' do
project = create(:project)
runner = create(:ci_runner, :project, projects: [project])
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'specific'
expect(page).to have_text '1'
end
end
end
describe 'runners registration token' do
let!(:token) { Gitlab::CurrentSettings.runners_registration_token }
......@@ -286,17 +281,23 @@ RSpec.describe "Admin Runners" do
end
it 'has a registration token' do
expect(page.find('[data-testid="registration_token"]')).to have_content(token)
click_on 'Click to reveal'
expect(page.find('[data-testid="registration-token"]')).to have_content(token)
end
describe 'reset registration token' do
let(:page_token) { find('[data-testid="registration_token"]').text }
let(:page_token) { find('[data-testid="registration-token"]').text }
before do
click_button 'Reset registration token'
page.accept_alert
wait_for_requests
end
it 'changes registration token' do
click_on 'Click to reveal'
expect(page_token).not_to eq token
end
end
......@@ -409,4 +410,43 @@ RSpec.describe "Admin Runners" do
end
end
end
private
def search_bar_selector
'[data-testid="runners-filtered-search"]'
end
# The filters must be clicked first to be able to receive events
# See: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1493
def focus_filtered_search
page.within(search_bar_selector) do
page.find('.gl-filtered-search-term-token').click
end
end
def input_filtered_search_keys(search_term)
focus_filtered_search
page.within(search_bar_selector) do
page.find('input').send_keys(search_term)
click_on 'Search'
end
end
def input_filtered_search_filter_is_only(filter, value)
focus_filtered_search
page.within(search_bar_selector) do
click_on filter
# For OPERATOR_IS_ONLY, clicking the filter
# immediately preselects "=" operator
page.find('input').send_keys(value)
page.find('input').send_keys(:enter)
click_on 'Search'
end
end
end
......@@ -13,6 +13,7 @@ describe('RunnerList', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
const findSortOptions = () => wrapper.findAllComponents(GlDropdownItem);
const findActiveRunnersMessage = () => wrapper.findByTestId('active-runners-message');
const mockDefaultSort = 'CREATED_DESC';
const mockOtherSort = 'CONTACTED_DESC';
......@@ -20,6 +21,7 @@ describe('RunnerList', () => {
{ type: PARAM_KEY_STATUS, value: { data: 'ACTIVE', operator: '=' } },
{ type: 'filtered-search-term', value: { data: '' } },
];
const mockActiveRunnersCount = 2;
const createComponent = ({ props = {}, options = {} } = {}) => {
wrapper = extendedWrapper(
......@@ -30,6 +32,7 @@ describe('RunnerList', () => {
filters: [],
sort: mockDefaultSort,
},
activeRunnersCount: mockActiveRunnersCount,
...props,
},
stubs: {
......@@ -55,6 +58,18 @@ describe('RunnerList', () => {
expect(findFilteredSearch().props('namespace')).toBe('runners');
});
it('Displays an active runner count', () => {
expect(findActiveRunnersMessage().text()).toBe(
`Runners currently online: ${mockActiveRunnersCount}`,
);
});
it('Displays a large active runner count', () => {
createComponent({ props: { activeRunnersCount: 2000 } });
expect(findActiveRunnersMessage().text()).toBe('Runners currently online: 2,000');
});
it('sets sorting options', () => {
const SORT_OPTIONS_COUNT = 2;
......
......@@ -12,7 +12,6 @@ const mockActiveRunnersCount = mockRunners.length;
describe('RunnerList', () => {
let wrapper;
const findActiveRunnersMessage = () => wrapper.findByTestId('active-runners-message');
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findTable = () => wrapper.findComponent(GlTable);
const findHeaders = () => wrapper.findAll('th');
......@@ -40,18 +39,6 @@ describe('RunnerList', () => {
wrapper.destroy();
});
it('Displays active runner count', () => {
expect(findActiveRunnersMessage().text()).toBe(
`Runners currently online: ${mockActiveRunnersCount}`,
);
});
it('Displays a large active runner count', () => {
createComponent({ props: { activeRunnersCount: 2000 } });
expect(findActiveRunnersMessage().text()).toBe('Runners currently online: 2,000');
});
it('Displays headers', () => {
const headerLabels = findHeaders().wrappers.map((w) => w.text());
......
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