Commit c78d120c authored by Phil Hughes's avatar Phil Hughes

Merge branch 'add-group-runners-views-controllers' into 'master'

Add controller and views for manage organization's Runners on group level

See merge request gitlab-org/gitlab!33074
parents 38caa7f0 ca856318
......@@ -32,6 +32,7 @@ export default class FilteredSearchManager {
filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
placeholder = __('Search or filter results...'),
anchor = null,
}) {
this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
......@@ -47,6 +48,7 @@ export default class FilteredSearchManager {
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.stateFiltersSelector = stateFiltersSelector;
this.placeholder = placeholder;
this.anchor = anchor;
const { multipleAssignees } = this.filteredSearchInput.dataset;
if (multipleAssignees && this.filteredSearchTokenKeys.enableMultipleAssignees) {
......@@ -779,7 +781,11 @@ export default class FilteredSearchManager {
paths.push(`search=${sanitized}`);
}
const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
let parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
if (this.anchor) {
parameterizedUrl += `#${this.anchor}`;
}
if (this.updateObject) {
this.updateObject(parameterizedUrl);
......
......@@ -4,4 +4,5 @@ export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
ADMIN_RUNNERS: 'admin/runners',
GROUP_RUNNERS_ANCHOR: 'runners-settings',
};
import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
});
if (gon.features.newVariablesUi) {
initVariableList();
} else {
......
......@@ -7,6 +7,7 @@ export default ({
isGroupAncestor,
isGroupDecendent,
stateFiltersSelector,
anchor,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
......@@ -17,6 +18,7 @@ export default ({
isGroupDecendent,
filteredSearchTokenKeys,
stateFiltersSelector,
anchor,
});
filteredSearchManager.setup();
}
......
......@@ -23,10 +23,14 @@ class Groups::RunnersController < Groups::ApplicationController
end
def destroy
if @runner.belongs_to_more_than_one_project?
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.')
else
@runner.destroy
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found
end
end
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
......@@ -47,7 +51,9 @@ class Groups::RunnersController < Groups::ApplicationController
private
def runner
@runner ||= @group.runners.find(params[:id])
@runner ||= Ci::RunnersFinder.new(current_user: current_user, group: @group, params: {}).execute
.except(:limit, :offset)
.find(params[:id])
end
def runner_params
......
......@@ -11,7 +11,15 @@ module Groups
end
before_action :define_variables, only: [:show]
NUMBER_OF_RUNNERS_PER_PAGE = 4
def show
runners_finder = Ci::RunnersFinder.new(current_user: current_user, group: @group, params: params)
# We need all runners for count
@all_group_runners = runners_finder.execute.except(:limit, :offset)
@group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
@sort = runners_finder.sort_key
end
def update
......
......@@ -239,6 +239,10 @@ module Ci
runner_projects.count == 1
end
def belongs_to_more_than_one_project?
self.projects.limit(2).count(:all) > 1
end
def assigned_to_group?
runner_namespaces.any?
end
......
......@@ -18,13 +18,3 @@
locals: { registration_token: @group.runners_token,
type: 'group',
reset_token_url: reset_registration_token_group_settings_ci_cd_path }
- if @group.runners.empty?
%h4.underlined-title
= _('This group does not provide any group Runners yet.')
- else
%h4.underlined-title
= _('Available group Runners: %{runners}').html_safe % { runners: @group.runners.count }
%ul.bordered-list
= render partial: 'groups/runners/runner', collection: @group.runners, as: :runner
......@@ -7,3 +7,97 @@
.row
.col-sm-6
= render 'groups/runners/group_runners'
%h4.underlined-title
= _('Available Runners: %{runners}').html_safe % { runners: limited_counter_with_delimiter(@all_group_runners) }
-# haml-lint:disable NoPlainNodes
.row
.col-sm-9
= form_tag group_settings_ci_cd_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
.filtered-search-wrapper.d-flex
.filtered-search-box
= dropdown_tag(_('Recent searches'),
options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
toggle_class: 'btn filtered-search-history-dropdown-toggle-button',
dropdown_class: 'filtered-search-history-dropdown',
content_class: 'filtered-search-history-dropdown-content' }) do
.js-filtered-search-history-dropdown{ data: { full_path: group_settings_ci_cd_path } }
.filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
%input.form-control.filtered-search{ search_filter_input_options('runners') }
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
= button_tag class: 'btn btn-link' do
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
-# haml lint's ClassAttributeWithStaticValue
%svg
%use{ 'xlink:href': "#{'{{icon}}'}" }
%span.js-filter-hint
{{formattedKey}}
#js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
%li.filter-dropdown-item{ data: { value: "{{ title }}" } }
= button_tag class: 'btn btn-link' do
{{ title }}
%span.btn-helptext
{{ help }}
#js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
- Ci::Runner::AVAILABLE_STATUSES.each do |status|
%li.filter-dropdown-item{ data: { value: status } }
= button_tag class: 'btn btn-link' do
= status.titleize
#js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
- Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
- next if runner_type == 'instance_type'
%li.filter-dropdown-item{ data: { value: runner_type } }
= button_tag class: 'btn btn-link' do
= runner_type.titleize
#js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
= button_tag class: 'btn btn-link' do
= _('No Tag')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
= button_tag class: 'btn btn-link js-data-value' do
%span.dropdown-light-content
{{name}}
= button_tag class: 'clear-search hidden' do
= icon('times')
.filter-dropdown-container
= render 'admin/runners/sort_dropdown'
.col-sm-3.text-right-lg
= _('Runners currently online: %{active_runners_count}') % { active_runners_count: limited_counter_with_delimiter(@all_group_runners.online) }
- if @group_runners.any?
.runners-content.content-list
.table-holder
.gl-responsive-table-row.table-row-header{ role: 'row' }
.table-section.section-10{ role: 'rowheader' }= _('Type/State')
.table-section.section-10{ role: 'rowheader' }= _('Runner token')
.table-section.section-20{ role: 'rowheader' }= _('Description')
.table-section.section-10{ role: 'rowheader' }= _('Version')
.table-section.section-10{ role: 'rowheader' }= _('IP Address')
.table-section.section-5{ role: 'rowheader' }= _('Projects')
.table-section.section-5{ role: 'rowheader' }= _('Jobs')
.table-section.section-10{ role: 'rowheader' }= _('Tags')
.table-section.section-10{ role: 'rowheader' }= _('Last contact')
.table-section.section-10{ role: 'rowheader' }
- @group_runners.each do |runner|
= render 'groups/runners/runner', runner: runner
= paginate @group_runners, theme: 'gitlab', :params => { :anchor => 'runners-settings' }
- else
.nothing-here-block= _('No runners found')
%li.runner{ id: dom_id(runner) }
%h4
= runner_status_icon(runner)
.gl-responsive-table-row{ id: dom_id(runner) }
.table-section.section-10.section-wrap
.table-mobile-header{ role: 'rowheader' }= _('Type')
.table-mobile-content
- if runner.group_type?
%span.badge.badge-success
= _('group')
- else
%span.badge.badge-info
= _('specific')
- if runner.locked?
%span.badge.badge-warning
= _('locked')
- unless runner.active?
%span.badge.badge-danger
= _('paused')
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Runner token')
.table-mobile-content
= link_to runner.short_sha, group_runner_path(@group, runner)
= link_to runner.short_sha, group_runner_path(@group, runner), class: 'commit-sha'
.table-section.section-20
.table-mobile-header{ role: 'rowheader' }= _('Description')
.table-mobile-content.str-truncated.has-tooltip{ title: runner.description }
= runner.description
%small.edit-runner
= link_to edit_group_runner_path(@group, runner) do
= icon('edit')
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Version')
.table-mobile-content.str-truncated.has-tooltip{ title: runner.version }
= runner.version
.float-right
- if runner.active?
= link_to _('Pause'), pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: _("Are you sure?") }
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('IP Address')
.table-mobile-content.str-truncated.has-tooltip{ title: runner.ip_address }
= runner.ip_address
.table-section.section-5
.table-mobile-header{ role: 'rowheader' }= _('Projects')
.table-mobile-content
- if runner.group_type?
= _('n/a')
- else
= link_to _('Resume'), resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-success btn-sm'
= link_to _('Remove Runner'), group_runner_path(@group, runner), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm'
.float-right
%small.light
\##{runner.id}
- if runner.description.present?
%p.runner-description
= runner.description
- if runner.tag_list.present?
%p
- runner.tag_list.sort.each do |tag|
%span.label.label-primary
= runner.projects.count(:all)
.table-section.section-5
.table-mobile-header{ role: 'rowheader' }= _('Jobs')
.table-mobile-content
= limited_counter_with_delimiter(runner.builds)
.table-section.section-10.section-wrap
.table-mobile-header{ role: 'rowheader' }= _('Tags')
.table-mobile-content
- runner.tags.map(&:name).sort.each do |tag|
%span.badge.badge-primary.str-truncated.has-tooltip{ title: tag }
= tag
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Last contact')
.table-mobile-content
- contacted_at = runner_contacted_at(runner)
- if contacted_at
= time_ago_with_tooltip contacted_at
- else
= _('Never')
.table-section.table-button-footer.section-10
.btn-group.table-action-buttons
.btn-group
= link_to edit_group_runner_path(@group, runner), class: 'btn btn-default has-tooltip', title: _('Edit'), ref: 'tooltip', aria: { label: _('Edit') }, data: { placement: 'top', container: 'body'} do
= icon('pencil')
.btn-group
- if runner.active?
= link_to pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= icon('pause')
- else
= link_to resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
= icon('play')
- if runner.belongs_to_more_than_one_project?
.btn-group
.btn.btn-danger.has-tooltip{ 'aria-label' => 'Remove', 'data-container' => 'body', 'data-original-title' => _('Multi-project Runners cannot be removed'), 'data-placement' => 'top', disabled: 'disabled' }
= icon('remove')
- else
.btn-group
= link_to group_runner_path(@group, runner), method: :delete, class: 'btn btn-danger has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= icon('remove')
......@@ -3507,6 +3507,9 @@ msgstr ""
msgid "Available"
msgstr ""
msgid "Available Runners: %{runners}"
msgstr ""
msgid "Available for dependency and container scanning"
msgstr ""
......@@ -15333,6 +15336,9 @@ msgstr ""
msgid "Multi-project"
msgstr ""
msgid "Multi-project Runners cannot be removed"
msgstr ""
msgid "Multiple IP address ranges are supported."
msgstr ""
......@@ -20278,6 +20284,9 @@ msgstr ""
msgid "Runner tokens"
msgstr ""
msgid "Runner was not deleted because it is assigned to multiple projects."
msgstr ""
msgid "Runner was not updated."
msgstr ""
......@@ -28167,6 +28176,9 @@ msgstr ""
msgid "loading"
msgstr ""
msgid "locked"
msgstr ""
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
......@@ -28587,6 +28599,9 @@ msgstr[1] ""
msgid "password"
msgstr ""
msgid "paused"
msgstr ""
msgid "pending comment"
msgstr ""
......@@ -28758,6 +28773,9 @@ msgstr ""
msgid "source diff"
msgstr ""
msgid "specific"
msgstr ""
msgid "specified top is not part of the tree"
msgstr ""
......
......@@ -6,6 +6,9 @@ RSpec.describe Groups::RunnersController do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
let(:project) { create(:project, group: group) }
let(:runner_project) { create(:ci_runner, :project, projects: [project]) }
let(:params_runner_project) { { group_id: group, id: runner_project } }
let(:params) { { group_id: group, id: runner } }
before do
......@@ -24,6 +27,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
it 'renders show with 200 status code project runner' do
get :show, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
end
context 'when user is not owner' do
......@@ -36,6 +46,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'renders a 404 project runner' do
get :show, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
......@@ -51,6 +67,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
end
it 'renders show with 200 status code project runner' do
get :edit, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
end
end
context 'when user is not owner' do
......@@ -63,6 +86,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'renders a 404 project runner' do
get :edit, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
......@@ -82,6 +111,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.description).to eq(new_desc)
end
it 'updates the project runner, ticks the queue, and redirects project runner' do
new_desc = runner_project.description.swapcase
expect do
post :update, params: params_runner_project.merge(runner: { description: new_desc } )
end.to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:found)
expect(runner_project.reload.description).to eq(new_desc)
end
end
context 'when user is not an owner' do
......@@ -99,6 +139,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.description).to eq(old_desc)
end
it 'rejects the update and responds 404 project runner' do
old_desc = runner_project.description
expect do
post :update, params: params_runner_project.merge(runner: { description: old_desc.swapcase } )
end.not_to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:not_found)
expect(runner_project.reload.description).to eq(old_desc)
end
end
end
......@@ -114,6 +165,31 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
end
it 'destroys the project runner and redirects' do
delete :destroy, params: params_runner_project
expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner_project.id)).to be_nil
end
end
context 'when user is an owner and runner in multiple projects' do
let(:project_2) { create(:project, group: group) }
let(:runner_project_2) { create(:ci_runner, :project, projects: [project, project_2]) }
let(:params_runner_project_2) { { group_id: group, id: runner_project_2 } }
before do
group.add_owner(user)
end
it 'does not destroy the project runner' do
delete :destroy, params: params_runner_project_2
expect(response).to have_gitlab_http_status(:found)
expect(flash[:alert]).to eq('Runner was not deleted because it is assigned to multiple projects.')
expect(Ci::Runner.find_by(id: runner_project_2.id)).to be_present
end
end
context 'when user is not an owner' do
......@@ -127,6 +203,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner.id)).to be_present
end
it 'responds 404 and does not destroy the project runner' do
delete :destroy, params: params_runner_project
expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner_project.id)).to be_present
end
end
end
......@@ -146,6 +229,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(true)
end
it 'marks the project runner as active, ticks the queue, and redirects' do
runner_project.update(active: false)
expect do
post :resume, params: params_runner_project
end.to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:found)
expect(runner_project.reload.active).to eq(true)
end
end
context 'when user is not an owner' do
......@@ -163,6 +257,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(false)
end
it 'responds 404 and does not activate the project runner' do
runner_project.update(active: false)
expect do
post :resume, params: params_runner_project
end.not_to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:not_found)
expect(runner_project.reload.active).to eq(false)
end
end
end
......@@ -182,6 +287,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(false)
end
it 'marks the project runner as inactive, ticks the queue, and redirects' do
runner_project.update(active: true)
expect do
post :pause, params: params_runner_project
end.to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:found)
expect(runner_project.reload.active).to eq(false)
end
end
context 'when user is not an owner' do
......@@ -199,6 +315,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(true)
end
it 'responds 404 and does not update the project runner or queue' do
runner_project.update(active: true)
expect do
post :pause, params: params
end.not_to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:not_found)
expect(runner_project.reload.active).to eq(true)
end
end
end
end
......@@ -5,8 +5,15 @@ require 'spec_helper'
RSpec.describe Groups::Settings::CiCdController do
include ExternalAuthorizationServiceHelpers
let(:group) { create(:group) }
let(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:sub_group) { create(:group, parent: group) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:project_2) { create(:project, group: sub_group) }
let_it_be(:runner_group) { create(:ci_runner, :group, groups: [group]) }
let_it_be(:runner_project_1) { create(:ci_runner, :project, projects: [project])}
let_it_be(:runner_project_2) { create(:ci_runner, :project, projects: [project_2])}
let_it_be(:runner_project_3) { create(:ci_runner, :project, projects: [project, project_2])}
before do
sign_in(user)
......@@ -18,11 +25,12 @@ RSpec.describe Groups::Settings::CiCdController do
group.add_owner(user)
end
it 'renders show with 200 status code' do
it 'renders show with 200 status code and correct runners' do
get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
expect(assigns(:group_runners)).to match_array([runner_group, runner_project_1, runner_project_2, runner_project_3])
end
end
......@@ -35,6 +43,7 @@ RSpec.describe Groups::Settings::CiCdController do
get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
expect(assigns(:group_runners)).to be_nil
end
end
......
......@@ -270,7 +270,7 @@ RSpec.describe 'Runners' do
it 'there are no runners displayed' do
visit group_settings_ci_cd_path(group)
expect(page).to have_content 'This group does not provide any group Runners yet'
expect(page).to have_content 'No runners found'
end
it 'user can see a link to install runners on kubernetes clusters' do
......@@ -286,26 +286,26 @@ RSpec.describe 'Runners' do
it 'the runner is visible' do
visit group_settings_ci_cd_path(group)
expect(page).not_to have_content 'This group does not provide any group Runners yet'
expect(page).to have_content 'Available group Runners: 1'
expect(page).not_to have_content 'No runners found'
expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'group-runner'
end
it 'user can pause and resume the group runner' do
visit group_settings_ci_cd_path(group)
expect(page).to have_content('Pause')
expect(page).not_to have_content('Resume')
expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: resume_group_runner_path(group, runner)
click_on 'Pause'
click_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_content('Pause')
expect(page).to have_content('Resume')
expect(page).not_to have_link href: pause_group_runner_path(group, runner)
expect(page).to have_link href: resume_group_runner_path(group, runner)
click_on 'Resume'
click_link href: resume_group_runner_path(group, runner)
expect(page).to have_content('Pause')
expect(page).not_to have_content('Resume')
expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: resume_group_runner_path(group, runner)
end
it 'user can view runner details' do
......@@ -321,7 +321,7 @@ RSpec.describe 'Runners' do
it 'user can remove a group runner' do
visit group_settings_ci_cd_path(group)
click_on 'Remove Runner'
all(:link, href: group_runner_path(group, runner))[1].click
expect(page).not_to have_content(runner.display_name)
end
......@@ -329,7 +329,7 @@ RSpec.describe 'Runners' do
it 'user edits the runner to be protected' do
visit group_settings_ci_cd_path(group)
first('.edit-runner > a').click
click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[access_level]')).not_to be_checked
......@@ -347,7 +347,7 @@ RSpec.describe 'Runners' do
it 'user edits runner not to run untagged jobs' do
visit group_settings_ci_cd_path(group)
first('.edit-runner > a').click
click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[run_untagged]')).to be_checked
......@@ -358,5 +358,97 @@ RSpec.describe 'Runners' do
end
end
end
context 'group with a project runner' do
let(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project], description: 'project-runner') }
it 'the runner is visible' do
visit group_settings_ci_cd_path(group)
expect(page).not_to have_content 'No runners found'
expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'project-runner'
end
it 'user can pause and resume the project runner' do
visit group_settings_ci_cd_path(group)
expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: resume_group_runner_path(group, runner)
click_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: pause_group_runner_path(group, runner)
expect(page).to have_link href: resume_group_runner_path(group, runner)
click_link href: resume_group_runner_path(group, runner)
expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: resume_group_runner_path(group, runner)
end
it 'user can view runner details' do
visit group_settings_ci_cd_path(group)
expect(page).to have_content(runner.display_name)
click_on runner.short_sha
expect(page).to have_content(runner.platform)
end
it 'user can remove a project runner' do
visit group_settings_ci_cd_path(group)
all(:link, href: group_runner_path(group, runner))[1].click
expect(page).not_to have_content(runner.display_name)
end
it 'user edits the runner to be protected' do
visit group_settings_ci_cd_path(group)
click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[access_level]')).not_to be_checked
check 'runner_access_level'
click_button 'Save changes'
expect(page).to have_content 'Protected Yes'
end
context 'when a runner has a tag' do
before do
runner.update(tag_list: ['tag'])
end
it 'user edits runner not to run untagged jobs' do
visit group_settings_ci_cd_path(group)
click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[run_untagged]')).to be_checked
uncheck 'runner_run_untagged'
click_button 'Save changes'
expect(page).to have_content 'Can run untagged jobs No'
end
end
end
context 'group with a multi-project runner' do
let(:project) { create(:project, group: group) }
let(:project_2) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project, project_2], description: 'group-runner') }
it 'user cannot remove the project runner' do
visit group_settings_ci_cd_path(group)
expect(all(:link, href: group_runner_path(group, runner)).length).to eq(1)
end
end
end
end
......@@ -713,6 +713,46 @@ RSpec.describe Ci::Runner do
end
end
describe '#belongs_to_more_than_one_project?' do
context 'project runner' do
let(:project1) { create(:project) }
let(:project2) { create(:project) }
context 'two projects assigned to runner' do
let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
it 'returns true' do
expect(runner.belongs_to_more_than_one_project?).to be_truthy
end
end
context 'one project assigned to runner' do
let(:runner) { create(:ci_runner, :project, projects: [project1]) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
end
context 'group runner' do
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
context 'shared runner' do
let(:runner) { create(:ci_runner, :instance) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
end
describe '#has_tags?' do
context 'when runner has tags' do
subject { create(:ci_runner, tag_list: ['tag']) }
......
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