Commit aa621050 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 058e5f4b ef48555e
......@@ -11,6 +11,12 @@ export default {
GlIcon,
},
props: {
showHeader: {
type: Boolean,
required: false,
default: true,
},
sectionTitle: {
type: String,
required: true,
......@@ -84,7 +90,7 @@ export default {
<template>
<div>
<gl-dropdown-section-header>
<gl-dropdown-section-header v-if="showHeader">
<div class="gl-display-flex align-items-center" data-testid="section-header">
<span class="gl-mr-2 gl-mb-1">{{ sectionTitle }}</span>
<gl-badge variant="neutral">{{ totalCountText }}</gl-badge>
......
......@@ -8,9 +8,16 @@ import {
GlIcon,
GlLoadingIcon,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { debounce, isArray } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
import { SEARCH_DEBOUNCE_MS, DEFAULT_I18N } from '../constants';
import {
ALL_REF_TYPES,
SEARCH_DEBOUNCE_MS,
DEFAULT_I18N,
REF_TYPE_BRANCHES,
REF_TYPE_TAGS,
REF_TYPE_COMMITS,
} from '../constants';
import createStore from '../stores';
import RefResultsSection from './ref_results_section.vue';
......@@ -28,6 +35,20 @@ export default {
RefResultsSection,
},
props: {
enabledRefTypes: {
type: Array,
required: false,
default: () => ALL_REF_TYPES,
validator: (val) =>
// It has to be an arrray
isArray(val) &&
// with at least one item
val.length > 0 &&
// and only "REF_TYPE_BRANCHES", "REF_TYPE_TAGS", and "REF_TYPE_COMMITS" are allowed
val.every((item) => ALL_REF_TYPES.includes(item)) &&
// and no duplicates are allowed
val.length === new Set(val).size,
},
value: {
type: String,
required: false,
......@@ -62,17 +83,29 @@ export default {
};
},
showBranchesSection() {
return Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error);
return (
this.enabledRefTypes.includes(REF_TYPE_BRANCHES) &&
Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error)
);
},
showTagsSection() {
return Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error);
return (
this.enabledRefTypes.includes(REF_TYPE_TAGS) &&
Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error)
);
},
showCommitsSection() {
return Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error);
return (
this.enabledRefTypes.includes(REF_TYPE_COMMITS) &&
Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error)
);
},
showNoResults() {
return !this.showBranchesSection && !this.showTagsSection && !this.showCommitsSection;
},
showSectionHeaders() {
return this.enabledRefTypes.length > 1;
},
},
watch: {
// Keep the Vuex store synchronized if the parent
......@@ -97,10 +130,18 @@ export default {
}, SEARCH_DEBOUNCE_MS);
this.setProjectId(this.projectId);
this.search(this.query);
this.$watch(
'enabledRefTypes',
() => {
this.setEnabledRefTypes(this.enabledRefTypes);
this.search(this.query);
},
{ immediate: true },
);
},
methods: {
...mapActions(['setProjectId', 'setSelectedRef', 'search']),
...mapActions(['setEnabledRefTypes', 'setProjectId', 'setSelectedRef', 'search']),
focusSearchBox() {
this.$refs.searchBox.$el.querySelector('input').focus();
},
......@@ -170,6 +211,7 @@ export default {
:selected-ref="selectedRef"
:error="matches.branches.error"
:error-message="i18n.branchesErrorMessage"
:show-header="showSectionHeaders"
data-testid="branches-section"
@selected="selectRef($event)"
/>
......@@ -185,6 +227,7 @@ export default {
:selected-ref="selectedRef"
:error="matches.tags.error"
:error-message="i18n.tagsErrorMessage"
:show-header="showSectionHeaders"
data-testid="tags-section"
@selected="selectRef($event)"
/>
......@@ -200,6 +243,7 @@ export default {
:selected-ref="selectedRef"
:error="matches.commits.error"
:error-message="i18n.commitsErrorMessage"
:show-header="showSectionHeaders"
data-testid="commits-section"
@selected="selectRef($event)"
/>
......
import { __ } from '~/locale';
export const REF_TYPE_BRANCHES = 'REF_TYPE_BRANCHES';
export const REF_TYPE_TAGS = 'REF_TYPE_TAGS';
export const REF_TYPE_COMMITS = 'REF_TYPE_COMMITS';
export const ALL_REF_TYPES = Object.freeze([REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS]);
export const X_TOTAL_HEADER = 'x-total';
export const SEARCH_DEBOUNCE_MS = 250;
......
import Api from '~/api';
import { REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS } from '../constants';
import * as types from './mutation_types';
export const setEnabledRefTypes = ({ commit }, refTypes) =>
commit(types.SET_ENABLED_REF_TYPES, refTypes);
export const setProjectId = ({ commit }, projectId) => commit(types.SET_PROJECT_ID, projectId);
export const setSelectedRef = ({ commit }, selectedRef) =>
commit(types.SET_SELECTED_REF, selectedRef);
export const search = ({ dispatch, commit }, query) => {
export const search = ({ state, dispatch, commit }, query) => {
commit(types.SET_QUERY, query);
dispatch('searchBranches');
dispatch('searchTags');
dispatch('searchCommits');
const dispatchIfRefTypeEnabled = (refType, action) => {
if (state.enabledRefTypes.includes(refType)) {
dispatch(action);
}
};
dispatchIfRefTypeEnabled(REF_TYPE_BRANCHES, 'searchBranches');
dispatchIfRefTypeEnabled(REF_TYPE_TAGS, 'searchTags');
dispatchIfRefTypeEnabled(REF_TYPE_COMMITS, 'searchCommits');
};
export const searchBranches = ({ commit, state }) => {
......
export const SET_ENABLED_REF_TYPES = 'SET_ENABLED_REF_TYPES';
export const SET_PROJECT_ID = 'SET_PROJECT_ID';
export const SET_SELECTED_REF = 'SET_SELECTED_REF';
export const SET_QUERY = 'SET_QUERY';
......
......@@ -4,6 +4,9 @@ import { X_TOTAL_HEADER } from '../constants';
import * as types from './mutation_types';
export default {
[types.SET_ENABLED_REF_TYPES](state, refTypes) {
state.enabledRefTypes = refTypes;
},
[types.SET_PROJECT_ID](state, projectId) {
state.projectId = projectId;
},
......
const createRefTypeState = () => ({
list: [],
totalCount: 0,
error: null,
});
export default () => ({
enabledRefTypes: [],
projectId: null,
query: '',
matches: {
branches: {
list: [],
totalCount: 0,
error: null,
},
tags: {
list: [],
totalCount: 0,
error: null,
},
commits: {
list: [],
totalCount: 0,
error: null,
},
branches: createRefTypeState(),
tags: createRefTypeState(),
commits: createRefTypeState(),
},
selectedRef: null,
requestCount: 0,
......
......@@ -46,6 +46,8 @@ class RootController < Dashboard::ProjectsController
redirect_to(activity_dashboard_path)
when 'starred_project_activity'
redirect_to(activity_dashboard_path(filter: 'starred'))
when 'followed_user_activity'
redirect_to(activity_dashboard_path(filter: 'followed'))
when 'groups'
redirect_to(dashboard_groups_path)
when 'todos'
......
......@@ -29,6 +29,7 @@ module PreferencesHelper
stars: _("Starred Projects"),
project_activity: _("Your Projects' Activity"),
starred_project_activity: _("Starred Projects' Activity"),
followed_user_activity: _("Followed Users' Activity"),
groups: _("Your Groups"),
todos: _("Your To-Do List"),
issues: _("Assigned Issues"),
......
......@@ -272,7 +272,7 @@ class User < ApplicationRecord
enum layout: { fixed: 0, fluid: 1 }
# User's Dashboard preference
enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8 }
enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8, followed_user_activity: 9 }
# User's Project preference
enum project_view: { readme: 0, activity: 1, files: 2 }
......
......@@ -25,7 +25,11 @@
&bull;
- if total_count > recent_releases.count
&bull;
= link_to n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }, project_releases_path(milestone.project)
- more_text = n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }
- if milestone.project_milestone?
= link_to more_text, project_releases_path(milestone.project)
- else
= more_text
%div
= render('shared/milestone_expired', milestone: milestone)
- if milestone.group_milestone?
......
---
title: "Add 'Followed User Activity' as dashboard user choices"
merge_request: 55165
author: Benj Fassbind @randombenj
type: added
---
title: Add runners api context metadata
merge_request: 55089
author:
type: changed
......@@ -171,8 +171,7 @@ certain arguments may also increase the complexity of a query.
NOTE:
The complexity limits may be revised in future, and additionally, the complexity
of a query may be altered. Changes to complexity can happen on `X.0` or `X.6`
releases without a deprecation period.
of a query may be altered.
### Request timeout
......
......@@ -108,12 +108,13 @@ select few, the amount of activity on the default Dashboard page can be
overwhelming. Changing this setting allows you to redefine your default
dashboard.
You have 8 options here that you can use for your default dashboard view:
You can include the following options for your default dashboard view:
- Your projects (default)
- Starred projects
- Your projects' activity
- Starred projects' activity
- Followed Users' Activity
- Your groups
- Your [To-Do List](../todos.md)
- Assigned Issues
......
......@@ -9,7 +9,11 @@ module Ci
belongs_to :namespace
scope :current_month, -> { where(date: Time.current.utc.beginning_of_month) }
scope :current_month, -> { where(date: beginning_of_month) }
def self.beginning_of_month(time = Time.current)
time.utc.beginning_of_month
end
# We should pretty much always use this method to access data for the current month
# since this will lazily create an entry if it doesn't exist.
......
......@@ -9,7 +9,11 @@ module Ci
belongs_to :project
scope :current_month, -> { where(date: Time.current.beginning_of_month) }
scope :current_month, -> { where(date: beginning_of_month) }
def self.beginning_of_month(time = Time.current)
time.utc.beginning_of_month
end
# We should pretty much always use this method to access data for the current month
# since this will lazily create an entry if it doesn't exist.
......
---
title: Fix 500 error on group milestones page when milestones are associated to more
than 3 releases
merge_request: 55540
author:
type: fixed
......@@ -16,7 +16,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
end
it 'does not raise exception if unique index is not violated' do
expect { create(:ci_namespace_monthly_usage, namespace: namespace, date: 1.month.ago.utc.beginning_of_month) }
expect { create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(1.month.ago)) }
.to change { described_class.count }.by(1)
end
end
......@@ -31,7 +31,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
expect(subject.amount_used).to eq(0)
expect(subject.namespace).to eq(namespace)
expect(subject.date).to eq(Time.current.beginning_of_month)
expect(subject.date).to eq(described_class.beginning_of_month)
end
end
end
......@@ -42,7 +42,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
context 'when namespace usage exists for previous months' do
before do
create(:ci_namespace_monthly_usage, namespace: namespace, date: 2.months.ago.utc.beginning_of_month)
create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago))
end
it_behaves_like 'creates usage record'
......
......@@ -16,7 +16,7 @@ RSpec.describe Ci::Minutes::ProjectMonthlyUsage do
end
it 'does not raise exception if unique index is not violated' do
expect { create(:ci_project_monthly_usage, project: project, date: 1.month.ago.utc.beginning_of_month) }
expect { create(:ci_project_monthly_usage, project: project, date: described_class.beginning_of_month(1.month.ago)) }
.to change { described_class.count }.by(1)
end
end
......@@ -31,7 +31,7 @@ RSpec.describe Ci::Minutes::ProjectMonthlyUsage do
expect(subject.amount_used).to eq(0)
expect(subject.project).to eq(project)
expect(subject.date).to eq(Time.current.beginning_of_month)
expect(subject.date).to eq(described_class.beginning_of_month)
end
end
end
......@@ -42,7 +42,7 @@ RSpec.describe Ci::Minutes::ProjectMonthlyUsage do
context 'when project usage exists for previous months' do
before do
create(:ci_project_monthly_usage, project: project, date: 2.months.ago.utc.beginning_of_month)
create(:ci_project_monthly_usage, project: project, date: described_class.beginning_of_month(2.months.ago))
end
it_behaves_like 'creates usage record'
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'shared/milestones/_milestone.html.haml' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user).tap { |user| project.add_maintainer(user) } }
let_it_be(:releases) { create_list(:release, 4, project: project) }
let_it_be(:milestone) { nil }
let(:more_text) { '1 more release' }
let(:link_href) { project_releases_path(project) }
before do
stub_licensed_features(group_milestone_project_releases: true)
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:milestone).and_return(milestone)
allow(view).to receive(:issues_path).and_return('path/to/issues')
allow(view).to receive(:merge_requests_path).and_return('path/to/merge_requests')
end
context 'when a milestone is associated to a lot of releases' do
context 'when viewing a project milestone' do
let(:milestone) { create(:milestone, project: project, releases: releases) }
before do
assign(:project, project)
end
it 'renders "1 more release" as a link to the project\'s Releases page' do
render
expect(rendered).to have_link(more_text, href: link_href)
end
end
context 'when viewing a group milestone' do
let(:milestone) { create(:milestone, group: group, releases: releases) }
before do
assign(:group, group)
end
it 'renders "1 more release" as plain text instead of as a link', :aggregate_failures do
render
expect(rendered).not_to have_link(more_text, href: link_href)
expect(rendered).to have_content(more_text)
end
end
end
end
......@@ -34,12 +34,12 @@ module API
if runner_registration_token_valid?
# Create shared runner. Requires admin access
attributes.merge(runner_type: :instance_type)
elsif project = Project.find_by_runners_token(params[:token])
elsif @project = Project.find_by_runners_token(params[:token])
# Create a specific runner for the project
attributes.merge(runner_type: :project_type, projects: [project])
elsif group = Group.find_by_runners_token(params[:token])
attributes.merge(runner_type: :project_type, projects: [@project])
elsif @group = Group.find_by_runners_token(params[:token])
# Create a specific runner for the group
attributes.merge(runner_type: :group_type, groups: [group])
attributes.merge(runner_type: :group_type, groups: [@group])
else
forbidden!
end
......@@ -81,12 +81,7 @@ module API
end
resource :jobs do
before do
Gitlab::ApplicationContext.push(
user: -> { current_job&.user },
project: -> { current_job&.project }
)
end
before { set_application_context }
desc 'Request a job' do
success Entities::JobRequest::Response
......
......@@ -71,6 +71,26 @@ module API
header 'Job-Status', job.status
forbidden!(reason)
end
def set_application_context
if current_job
Gitlab::ApplicationContext.push(
user: -> { current_job.user },
project: -> { current_job.project }
)
elsif current_runner&.project_type?
Gitlab::ApplicationContext.push(
project: -> do
projects = current_runner.projects.limit(2) # rubocop: disable CodeReuse/ActiveRecord
projects.first if projects.length == 1
end
)
elsif current_runner&.group_type?
Gitlab::ApplicationContext.push(
namespace: -> { current_runner.groups.first }
)
end
end
end
end
end
......@@ -13201,6 +13201,9 @@ msgstr ""
msgid "Follow"
msgstr ""
msgid "Followed Users' Activity"
msgstr ""
msgid "Followed users"
msgstr ""
......
......@@ -68,6 +68,18 @@ RSpec.describe RootController do
end
end
context 'who has customized their dashboard setting for followed user activities' do
before do
user.dashboard = 'followed_user_activity'
end
it 'redirects to the activity list' do
get :index
expect(response).to redirect_to activity_dashboard_path(filter: 'followed')
end
end
context 'who has customized their dashboard setting for groups' do
before do
user.dashboard = 'groups'
......
......@@ -7,7 +7,13 @@ import { trimText } from 'helpers/text_helper';
import { ENTER_KEY } from '~/lib/utils/keys';
import { sprintf } from '~/locale';
import RefSelector from '~/ref/components/ref_selector.vue';
import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants';
import {
X_TOTAL_HEADER,
DEFAULT_I18N,
REF_TYPE_BRANCHES,
REF_TYPE_TAGS,
REF_TYPE_COMMITS,
} from '~/ref/constants';
import createStore from '~/ref/stores/';
const localVue = createLocalVue();
......@@ -26,6 +32,7 @@ describe('Ref selector component', () => {
let branchesApiCallSpy;
let tagsApiCallSpy;
let commitApiCallSpy;
let requestSpies;
const createComponent = (props = {}, attrs = {}) => {
wrapper = mount(RefSelector, {
......@@ -58,6 +65,7 @@ describe('Ref selector component', () => {
.mockReturnValue([200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]);
tagsApiCallSpy = jest.fn().mockReturnValue([200, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]);
commitApiCallSpy = jest.fn().mockReturnValue([200, fixtures.commit]);
requestSpies = { branchesApiCallSpy, tagsApiCallSpy, commitApiCallSpy };
mock
.onGet(`/api/v4/projects/${projectId}/repository/branches`)
......@@ -592,4 +600,86 @@ describe('Ref selector component', () => {
});
});
});
describe('with non-default ref types', () => {
it.each`
enabledRefTypes | reqsCalled | reqsNotCalled
${[REF_TYPE_BRANCHES]} | ${['branchesApiCallSpy']} | ${['tagsApiCallSpy', 'commitApiCallSpy']}
${[REF_TYPE_TAGS]} | ${['tagsApiCallSpy']} | ${['branchesApiCallSpy', 'commitApiCallSpy']}
${[REF_TYPE_COMMITS]} | ${[]} | ${['branchesApiCallSpy', 'tagsApiCallSpy', 'commitApiCallSpy']}
${[REF_TYPE_TAGS, REF_TYPE_COMMITS]} | ${['tagsApiCallSpy']} | ${['branchesApiCallSpy', 'commitApiCallSpy']}
`(
'only calls $reqsCalled requests when $enabledRefTypes are enabled',
async ({ enabledRefTypes, reqsCalled, reqsNotCalled }) => {
createComponent({ enabledRefTypes });
await waitForRequests();
reqsCalled.forEach((req) => expect(requestSpies[req]).toHaveBeenCalledTimes(1));
reqsNotCalled.forEach((req) => expect(requestSpies[req]).not.toHaveBeenCalled());
},
);
it('only calls commitApiCallSpy when REF_TYPE_COMMITS is enabled', async () => {
createComponent({ enabledRefTypes: [REF_TYPE_COMMITS] });
updateQuery('abcd1234');
await waitForRequests();
expect(commitApiCallSpy).toHaveBeenCalledTimes(1);
expect(branchesApiCallSpy).not.toHaveBeenCalled();
expect(tagsApiCallSpy).not.toHaveBeenCalled();
});
it('triggers another search if enabled ref types change', async () => {
createComponent({ enabledRefTypes: [REF_TYPE_BRANCHES] });
await waitForRequests();
expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
expect(tagsApiCallSpy).not.toHaveBeenCalled();
wrapper.setProps({
enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_TAGS],
});
await waitForRequests();
expect(branchesApiCallSpy).toHaveBeenCalledTimes(2);
expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
});
it('if a ref type becomes disabled, its section is hidden, even if it had some results in store', async () => {
createComponent({ enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_COMMITS] });
updateQuery('abcd1234');
await waitForRequests();
expect(findBranchesSection().exists()).toBe(true);
expect(findCommitsSection().exists()).toBe(true);
wrapper.setProps({ enabledRefTypes: [REF_TYPE_COMMITS] });
await waitForRequests();
expect(findBranchesSection().exists()).toBe(false);
expect(findCommitsSection().exists()).toBe(true);
});
it.each`
enabledRefType | findVisibleSection | findHiddenSections
${REF_TYPE_BRANCHES} | ${findBranchesSection} | ${[findTagsSection, findCommitsSection]}
${REF_TYPE_TAGS} | ${findTagsSection} | ${[findBranchesSection, findCommitsSection]}
${REF_TYPE_COMMITS} | ${findCommitsSection} | ${[findBranchesSection, findTagsSection]}
`(
'hides section headers if a single ref type is enabled',
async ({ enabledRefType, findVisibleSection, findHiddenSections }) => {
createComponent({ enabledRefTypes: [enabledRefType] });
updateQuery('abcd1234');
await waitForRequests();
expect(findVisibleSection().exists()).toBe(true);
expect(findVisibleSection().find('[data-testid="section-header"]').exists()).toBe(false);
findHiddenSections.forEach((findHiddenSection) =>
expect(findHiddenSection().exists()).toBe(false),
);
},
);
});
});
import testAction from 'helpers/vuex_action_helper';
import { ALL_REF_TYPES, REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS } from '~/ref/constants';
import * as actions from '~/ref/stores/actions';
import * as types from '~/ref/stores/mutation_types';
import createState from '~/ref/stores/state';
......@@ -25,6 +26,14 @@ describe('Ref selector Vuex store actions', () => {
state = createState();
});
describe('setEnabledRefTypes', () => {
it(`commits ${types.SET_ENABLED_REF_TYPES} with the enabled ref types`, () => {
testAction(actions.setProjectId, ALL_REF_TYPES, state, [
{ type: types.SET_PROJECT_ID, payload: ALL_REF_TYPES },
]);
});
});
describe('setProjectId', () => {
it(`commits ${types.SET_PROJECT_ID} with the new project ID`, () => {
const projectId = '4';
......@@ -46,12 +55,23 @@ describe('Ref selector Vuex store actions', () => {
describe('search', () => {
it(`commits ${types.SET_QUERY} with the new search query`, () => {
const query = 'hello';
testAction(actions.search, query, state, [{ type: types.SET_QUERY, payload: query }]);
});
it.each`
enabledRefTypes | expectedActions
${[REF_TYPE_BRANCHES]} | ${['searchBranches']}
${[REF_TYPE_COMMITS]} | ${['searchCommits']}
${[REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS]} | ${['searchBranches', 'searchTags', 'searchCommits']}
`(`dispatches fetch actions for enabled ref types`, ({ enabledRefTypes, expectedActions }) => {
const query = 'hello';
state.enabledRefTypes = enabledRefTypes;
testAction(
actions.search,
query,
state,
[{ type: types.SET_QUERY, payload: query }],
[{ type: 'searchBranches' }, { type: 'searchTags' }, { type: 'searchCommits' }],
expectedActions.map((type) => ({ type })),
);
});
});
......
import { X_TOTAL_HEADER } from '~/ref/constants';
import { X_TOTAL_HEADER, ALL_REF_TYPES } from '~/ref/constants';
import * as types from '~/ref/stores/mutation_types';
import mutations from '~/ref/stores/mutations';
import createState from '~/ref/stores/state';
......@@ -13,6 +13,7 @@ describe('Ref selector Vuex store mutations', () => {
describe('initial state', () => {
it('is created with the correct structure and initial values', () => {
expect(state).toEqual({
enabledRefTypes: [],
projectId: null,
query: '',
......@@ -39,6 +40,14 @@ describe('Ref selector Vuex store mutations', () => {
});
});
describe(`${types.SET_ENABLED_REF_TYPES}`, () => {
it('sets the enabled ref types', () => {
mutations[types.SET_ENABLED_REF_TYPES](state, ALL_REF_TYPES);
expect(state.enabledRefTypes).toBe(ALL_REF_TYPES);
});
});
describe(`${types.SET_PROJECT_ID}`, () => {
it('updates the project ID', () => {
const newProjectId = '4';
......
......@@ -29,6 +29,7 @@ RSpec.describe PreferencesHelper do
['Starred Projects', 'stars'],
["Your Projects' Activity", 'project_activity'],
["Starred Projects' Activity", 'starred_project_activity'],
["Followed Users' Activity", 'followed_user_activity'],
["Your Groups", 'groups'],
["Your To-Do List", 'todos'],
["Assigned Issues", 'issues'],
......
......@@ -797,6 +797,50 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
describe 'setting the application context' do
subject { request_job }
context 'when triggered by a user' do
let(:job) { create(:ci_build, user: user, project: project) }
subject { request_job(id: job.id) }
it_behaves_like 'storing arguments in the application context' do
let(:expected_params) { { user: user.username, project: project.full_path } }
end
it_behaves_like 'not executing any extra queries for the application context', 3 do
# Extra queries: User, Project, Route
let(:subject_proc) { proc { request_job(id: job.id) } }
end
end
context 'when the runner is of project type' do
it_behaves_like 'storing arguments in the application context' do
let(:expected_params) { { project: project.full_path } }
end
it_behaves_like 'not executing any extra queries for the application context', 2 do
# Extra queries: Project, Route
let(:subject_proc) { proc { request_job } }
end
end
context 'when the runner is of group type' do
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it_behaves_like 'storing arguments in the application context' do
let(:expected_params) { { root_namespace: group.full_path_components.first } }
end
it_behaves_like 'not executing any extra queries for the application context', 2 do
# Extra queries: Group, Route
let(:subject_proc) { proc { request_job } }
end
end
end
def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update)
post api('/jobs/request'), params: new_params.to_json, headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' }
......
......@@ -35,6 +35,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
context 'when valid token is provided' do
def request
post api('/runners'), params: { token: token }
end
it 'creates runner with default values' do
post api('/runners'), params: { token: registration_token }
......@@ -51,9 +55,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when project token is used' do
let(:project) { create(:project) }
let(:token) { project.runners_token }
it 'creates project runner' do
post api('/runners'), params: { token: project.runners_token }
request
expect(response).to have_gitlab_http_status(:created)
expect(project.runners.size).to eq(1)
......@@ -62,13 +67,24 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(runner.token).not_to eq(project.runners_token)
expect(runner).to be_project_type
end
it_behaves_like 'storing arguments in the application context' do
subject { request }
let(:expected_params) { { project: project.full_path } }
end
it_behaves_like 'not executing any extra queries for the application context' do
let(:subject_proc) { proc { request } }
end
end
context 'when group token is used' do
let(:group) { create(:group) }
let(:token) { group.runners_token }
it 'creates a group runner' do
post api('/runners'), params: { token: group.runners_token }
request
expect(response).to have_gitlab_http_status(:created)
expect(group.runners.reload.size).to eq(1)
......@@ -77,6 +93,16 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(runner.token).not_to eq(group.runners_token)
expect(runner).to be_group_type
end
it_behaves_like 'storing arguments in the application context' do
subject { request }
let(:expected_params) { { root_namespace: group.full_path_components.first } }
end
it_behaves_like 'not executing any extra queries for the application context' do
let(:subject_proc) { proc { request } }
end
end
end
......
......@@ -22,3 +22,19 @@ RSpec.shared_examples 'storing arguments in the application context' do
hash.transform_keys! { |key| "meta.#{key}" }
end
end
RSpec.shared_examples 'not executing any extra queries for the application context' do |expected_extra_queries = 0|
it 'does not execute more queries than without adding anything to the application context' do
# Call the subject once to memoize all factories being used for the spec, so they won't
# add any queries to the expectation.
subject_proc.call
expect do
allow(Gitlab::ApplicationContext).to receive(:push).and_call_original
subject_proc.call
end.to issue_same_number_of_queries_as {
allow(Gitlab::ApplicationContext).to receive(:push)
subject_proc.call
}.with_threshold(expected_extra_queries).ignoring_cached_queries
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