Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js2
-rw-r--r--app/assets/javascripts/diffs/store/modules/index.js4
-rw-r--r--app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js14
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js6
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js115
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js77
-rw-r--r--app/assets/javascripts/filtered_search/null_dropdown.js9
-rw-r--r--app/assets/javascripts/mr_notes/stores/index.js4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js17
-rw-r--r--app/assets/javascripts/notes/stores/index.js12
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js4
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js7
-rw-r--r--app/assets/javascripts/pages/admin/runners/index.js10
-rw-r--r--app/assets/javascripts/pages/constants.js1
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss3
-rw-r--r--app/controllers/admin/runners_controller.rb11
-rw-r--r--app/controllers/dashboard/milestones_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb1
-rw-r--r--app/controllers/projects/clusters_controller.rb3
-rw-r--r--app/finders/access_requests_finder.rb2
-rw-r--r--app/finders/admin/projects_finder.rb2
-rw-r--r--app/finders/admin/runners_finder.rb52
-rw-r--r--app/finders/branches_finder.rb2
-rw-r--r--app/finders/clusters_finder.rb2
-rw-r--r--app/finders/concerns/created_at_filter.rb2
-rw-r--r--app/finders/concerns/custom_attributes_filter.rb2
-rw-r--r--app/finders/concerns/finder_methods.rb2
-rw-r--r--app/finders/concerns/finder_with_cross_project_access.rb2
-rw-r--r--app/finders/contributed_projects_finder.rb2
-rw-r--r--app/finders/environments_finder.rb2
-rw-r--r--app/finders/events_finder.rb2
-rw-r--r--app/finders/fork_projects_finder.rb2
-rw-r--r--app/finders/group_descendants_finder.rb2
-rw-r--r--app/finders/group_finder.rb2
-rw-r--r--app/finders/group_members_finder.rb2
-rw-r--r--app/finders/group_projects_finder.rb2
-rw-r--r--app/finders/groups_finder.rb2
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/issues_finder.rb2
-rw-r--r--app/finders/joined_groups_finder.rb2
-rw-r--r--app/finders/labels_finder.rb2
-rw-r--r--app/finders/license_template_finder.rb2
-rw-r--r--app/finders/members_finder.rb2
-rw-r--r--app/finders/merge_request_target_project_finder.rb2
-rw-r--r--app/finders/merge_requests_finder.rb2
-rw-r--r--app/finders/milestones_finder.rb2
-rw-r--r--app/finders/notes_finder.rb2
-rw-r--r--app/finders/personal_access_tokens_finder.rb2
-rw-r--r--app/finders/personal_projects_finder.rb2
-rw-r--r--app/finders/pipeline_schedules_finder.rb2
-rw-r--r--app/finders/pipelines_finder.rb2
-rw-r--r--app/finders/projects_finder.rb2
-rw-r--r--app/finders/runner_jobs_finder.rb2
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/finders/tags_finder.rb2
-rw-r--r--app/finders/template_finder.rb2
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/finders/union_finder.rb2
-rw-r--r--app/finders/user_recent_events_finder.rb2
-rw-r--r--app/finders/users_finder.rb2
-rw-r--r--app/graphql/functions/base_function.rb2
-rw-r--r--app/graphql/functions/echo.rb2
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/graphql/mutations/concerns/mutations/resolves_project.rb2
-rw-r--r--app/graphql/mutations/merge_requests/base.rb2
-rw-r--r--app/graphql/resolvers/base_resolver.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_pipelines.rb2
-rw-r--r--app/graphql/resolvers/full_path_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_pipelines_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_resolver.rb2
-rw-r--r--app/graphql/resolvers/project_pipelines_resolver.rb2
-rw-r--r--app/graphql/resolvers/project_resolver.rb2
-rw-r--r--app/graphql/types/base_enum.rb2
-rw-r--r--app/graphql/types/base_field.rb2
-rw-r--r--app/graphql/types/base_input_object.rb2
-rw-r--r--app/graphql/types/base_interface.rb2
-rw-r--r--app/graphql/types/base_object.rb2
-rw-r--r--app/graphql/types/base_scalar.rb2
-rw-r--r--app/graphql/types/base_union.rb2
-rw-r--r--app/graphql/types/ci/pipeline_status_enum.rb2
-rw-r--r--app/graphql/types/ci/pipeline_type.rb2
-rw-r--r--app/graphql/types/merge_request_type.rb2
-rw-r--r--app/graphql/types/permission_types/base_permission_type.rb2
-rw-r--r--app/graphql/types/permission_types/ci/pipeline.rb2
-rw-r--r--app/graphql/types/permission_types/merge_request.rb2
-rw-r--r--app/graphql/types/permission_types/project.rb2
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/graphql/types/query_type.rb2
-rw-r--r--app/graphql/types/time_type.rb2
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/helpers/markup_helper.rb23
-rw-r--r--app/helpers/sorting_helper.rb11
-rw-r--r--app/models/ci/runner.rb22
-rw-r--r--app/models/clusters/applications/jupyter.rb9
-rw-r--r--app/models/concerns/protected_branch_access.rb11
-rw-r--r--app/models/concerns/protected_tag_access.rb3
-rw-r--r--app/models/dashboard_group_milestone.rb2
-rw-r--r--app/models/user.rb39
-rw-r--r--app/services/ci/fetch_kubernetes_token_service.rb75
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb50
-rw-r--r--app/services/clusters/gcp/kubernetes.rb13
-rw-r--r--app/services/clusters/gcp/kubernetes/create_service_account_service.rb51
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb30
-rw-r--r--app/services/clusters/gcp/provision_service.rb4
-rw-r--r--app/views/admin/runners/_runner.html.haml65
-rw-r--r--app/views/admin/runners/_runner_table_cell.html.haml4
-rw-r--r--app/views/admin/runners/_sort_dropdown.html.haml11
-rw-r--r--app/views/admin/runners/index.html.haml112
-rw-r--r--app/views/profiles/emails/index.html.haml8
-rw-r--r--app/views/profiles/show.html.haml3
-rw-r--r--app/views/projects/clusters/gcp/_form.html.haml10
-rw-r--r--app/views/projects/clusters/gcp/_show.html.haml9
-rw-r--r--app/views/projects/clusters/user/_form.html.haml1
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml2
121 files changed, 778 insertions, 305 deletions
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 39d90a64aab..eb596b251c1 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -11,8 +11,10 @@ export default () => ({
endpoint: '',
basePath: '',
commit: null,
+ startVersion: null,
diffFiles: [],
mergeRequestDiffs: [],
+ mergeRequestDiff: null,
diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
});
diff --git a/app/assets/javascripts/diffs/store/modules/index.js b/app/assets/javascripts/diffs/store/modules/index.js
index 20d1ebbe049..6860e24db6b 100644
--- a/app/assets/javascripts/diffs/store/modules/index.js
+++ b/app/assets/javascripts/diffs/store/modules/index.js
@@ -3,10 +3,10 @@ import * as getters from '../getters';
import mutations from '../mutations';
import createState from './diff_state';
-export default {
+export default () => ({
namespaced: true,
state: createState(),
getters,
actions,
mutations,
-};
+});
diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
new file mode 100644
index 00000000000..1f9c3f41e52
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
@@ -0,0 +1,14 @@
+import FilteredSearchTokenKeys from './filtered_search_token_keys';
+
+const tokenKeys = [{
+ key: 'status',
+ type: 'string',
+ param: 'status',
+ symbol: '',
+ icon: 'signal',
+ tag: 'status',
+}];
+
+const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
+
+export default AdminRunnersFilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 296571606d6..a750647f8be 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -7,6 +7,7 @@ import DropdownHint from './dropdown_hint';
import DropdownEmoji from './dropdown_emoji';
import DropdownNonUser from './dropdown_non_user';
import DropdownUser from './dropdown_user';
+import NullDropdown from './null_dropdown';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager {
@@ -90,6 +91,11 @@ export default class FilteredSearchDropdownManager {
gl: DropdownEmoji,
element: this.container.querySelector('#js-dropdown-my-reaction'),
},
+ status: {
+ reference: null,
+ gl: NullDropdown,
+ element: this.container.querySelector('#js-dropdown-admin-runner-status'),
+ },
};
supportedTokens.forEach((type) => {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 81286c54c4c..d25f6f95b22 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -3,10 +3,10 @@ import {
getParameterByName,
getUrlParamsArray,
} from '~/lib/utils/common_utils';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
-import FilteredSearchTokenKeys from './filtered_search_token_keys';
import RecentSearchesRoot from './recent_searches_root';
import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
@@ -23,7 +23,7 @@ export default class FilteredSearchManager {
isGroup = false,
isGroupAncestor = true,
isGroupDecendent = false,
- filteredSearchTokenKeys = FilteredSearchTokenKeys,
+ filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
}) {
this.isGroup = isGroup;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index 087ef5cd6f2..5d131b396a0 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -1,103 +1,38 @@
-const tokenKeys = [{
- key: 'author',
- type: 'string',
- param: 'username',
- symbol: '@',
- icon: 'pencil',
- tag: '@author',
-}, {
- key: 'assignee',
- type: 'string',
- param: 'username',
- symbol: '@',
- icon: 'user',
- tag: '@assignee',
-}, {
- key: 'milestone',
- type: 'string',
- param: 'title',
- symbol: '%',
- icon: 'clock-o',
- tag: '%milestone',
-}, {
- key: 'label',
- type: 'array',
- param: 'name[]',
- symbol: '~',
- icon: 'tag',
- tag: '~label',
-}];
-
-if (gon.current_user_id) {
- // Appending tokenkeys only logged-in
- tokenKeys.push({
- key: 'my-reaction',
- type: 'string',
- param: 'emoji',
- symbol: '',
- icon: 'thumbs-up',
- tag: 'emoji',
- });
-}
-
-const alternativeTokenKeys = [{
- key: 'label',
- type: 'string',
- param: 'name',
- symbol: '~',
-}];
-
-const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
+export default class FilteredSearchTokenKeys {
+ constructor(tokenKeys = [], alternativeTokenKeys = [], conditions = []) {
+ this.tokenKeys = tokenKeys;
+ this.alternativeTokenKeys = alternativeTokenKeys;
+ this.conditions = conditions;
-const conditions = [{
- url: 'assignee_id=0',
- tokenKey: 'assignee',
- value: 'none',
-}, {
- url: 'milestone_title=No+Milestone',
- tokenKey: 'milestone',
- value: 'none',
-}, {
- url: 'milestone_title=%23upcoming',
- tokenKey: 'milestone',
- value: 'upcoming',
-}, {
- url: 'milestone_title=%23started',
- tokenKey: 'milestone',
- value: 'started',
-}, {
- url: 'label_name[]=No+Label',
- tokenKey: 'label',
- value: 'none',
-}];
+ this.tokenKeysWithAlternative = this.tokenKeys.concat(this.alternativeTokenKeys);
+ }
-export default class FilteredSearchTokenKeys {
- static get() {
- return tokenKeys;
+ get() {
+ return this.tokenKeys;
}
- static getKeys() {
- return tokenKeys.map(i => i.key);
+ getKeys() {
+ return this.tokenKeys.map(i => i.key);
}
- static getAlternatives() {
- return alternativeTokenKeys;
+ getAlternatives() {
+ return this.alternativeTokenKeys;
}
- static getConditions() {
- return conditions;
+ getConditions() {
+ return this.conditions;
}
- static searchByKey(key) {
- return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
+ searchByKey(key) {
+ return this.tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
- static searchBySymbol(symbol) {
- return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
+ searchBySymbol(symbol) {
+ return this.tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
- static searchByKeyParam(keyParam) {
- return tokenKeysWithAlternative.find((tokenKey) => {
+ searchByKeyParam(keyParam) {
+ return this.tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
@@ -112,12 +47,12 @@ export default class FilteredSearchTokenKeys {
}) || null;
}
- static searchByConditionUrl(url) {
- return conditions.find(condition => condition.url === url) || null;
+ searchByConditionUrl(url) {
+ return this.conditions.find(condition => condition.url === url) || null;
}
- static searchByConditionKeyValue(key, value) {
- return conditions
+ searchByConditionKeyValue(key, value) {
+ return this.conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
new file mode 100644
index 00000000000..cce2c07dc3e
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -0,0 +1,77 @@
+import FilteredSearchTokenKeys from './filtered_search_token_keys';
+
+const tokenKeys = [{
+ key: 'author',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ icon: 'pencil',
+ tag: '@author',
+}, {
+ key: 'assignee',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ icon: 'user',
+ tag: '@assignee',
+}, {
+ key: 'milestone',
+ type: 'string',
+ param: 'title',
+ symbol: '%',
+ icon: 'clock-o',
+ tag: '%milestone',
+}, {
+ key: 'label',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ icon: 'tag',
+ tag: '~label',
+}];
+
+if (gon.current_user_id) {
+ // Appending tokenkeys only logged-in
+ tokenKeys.push({
+ key: 'my-reaction',
+ type: 'string',
+ param: 'emoji',
+ symbol: '',
+ icon: 'thumbs-up',
+ tag: 'emoji',
+ });
+}
+
+const alternativeTokenKeys = [{
+ key: 'label',
+ type: 'string',
+ param: 'name',
+ symbol: '~',
+}];
+
+const conditions = [{
+ url: 'assignee_id=0',
+ tokenKey: 'assignee',
+ value: 'none',
+}, {
+ url: 'milestone_title=No+Milestone',
+ tokenKey: 'milestone',
+ value: 'none',
+}, {
+ url: 'milestone_title=%23upcoming',
+ tokenKey: 'milestone',
+ value: 'upcoming',
+}, {
+ url: 'milestone_title=%23started',
+ tokenKey: 'milestone',
+ value: 'started',
+}, {
+ url: 'label_name[]=No+Label',
+ tokenKey: 'label',
+ value: 'none',
+}];
+
+const IssuableFilteredSearchTokenKeys =
+ new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions);
+
+export default IssuableFilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/null_dropdown.js b/app/assets/javascripts/filtered_search/null_dropdown.js
new file mode 100644
index 00000000000..4cfce2a5beb
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/null_dropdown.js
@@ -0,0 +1,9 @@
+import FilteredSearchDropdown from './filtered_search_dropdown';
+
+export default class NullDropdown extends FilteredSearchDropdown {
+ renderContent(forceShowList = false) {
+ this.droplab.changeHookList(this.hookId, this.dropdown, [], this.config);
+
+ super.renderContent(forceShowList);
+ }
+}
diff --git a/app/assets/javascripts/mr_notes/stores/index.js b/app/assets/javascripts/mr_notes/stores/index.js
index dd2019001db..446eb477efc 100644
--- a/app/assets/javascripts/mr_notes/stores/index.js
+++ b/app/assets/javascripts/mr_notes/stores/index.js
@@ -9,7 +9,7 @@ Vue.use(Vuex);
export default new Vuex.Store({
modules: {
page: mrPageModule,
- notes: notesModule,
- diffs: diffsModule,
+ notes: notesModule(),
+ diffs: diffsModule(),
},
});
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 9a2ec15debd..68df63b8539 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -10,6 +10,7 @@ import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
+import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
let eTagPoll;
@@ -61,9 +62,11 @@ export const refetchDiscussionById = ({ commit, state }, { path, discussionId })
.catch(() => {});
});
-export const deleteNote = ({ commit }, note) =>
+export const deleteNote = ({ commit, dispatch }, note) =>
service.deleteNote(note.path).then(() => {
commit(types.DELETE_NOTE, note);
+
+ dispatch('updateMergeRequestWidget');
});
export const updateNote = ({ commit }, { endpoint, note }) =>
@@ -84,20 +87,22 @@ export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
return res;
});
-export const createNewNote = ({ commit }, { endpoint, data }) =>
+export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
service
.createNewNote(endpoint, data)
.then(res => res.json())
.then(res => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
+
+ dispatch('updateMergeRequestWidget');
}
return res;
});
export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
-export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) =>
+export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) =>
service
.toggleResolveNote(endpoint, isResolved)
.then(res => res.json())
@@ -105,6 +110,8 @@ export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion
const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
commit(mutationType, res);
+
+ dispatch('updateMergeRequestWidget');
});
export const closeIssue = ({ commit, dispatch, state }) => {
@@ -333,5 +340,9 @@ export const fetchDiscussionDiffLines = ({ commit }, discussion) =>
});
});
+export const updateMergeRequestWidget = () => {
+ mrWidgetEventHub.$emit('mr.discussion.updated');
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js
index 0f48b8880f4..f105b7d0d11 100644
--- a/app/assets/javascripts/notes/stores/index.js
+++ b/app/assets/javascripts/notes/stores/index.js
@@ -1,16 +1,8 @@
import Vue from 'vue';
import Vuex from 'vuex';
-import * as actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-import module from './modules';
+import notesModule from './modules';
Vue.use(Vuex);
export default () =>
- new Vuex.Store({
- state: module.state,
- actions,
- getters,
- mutations,
- });
+ new Vuex.Store(notesModule());
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index b4cb9267e0f..61dbb075586 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -2,7 +2,7 @@ import * as actions from '../actions';
import * as getters from '../getters';
import mutations from '../mutations';
-export default {
+export default () => ({
state: {
discussions: [],
targetNoteHash: null,
@@ -24,4 +24,4 @@ export default {
actions,
getters,
mutations,
-};
+});
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 2c04bfea122..16bb54be126 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -214,12 +214,7 @@ export default {
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
- const index = state.discussions.indexOf(discussion);
- const discussionWithDiffLines = Object.assign({}, discussion, {
- truncated_diff_lines: diffLines,
- });
-
- state.discussions.splice(index, 1, discussionWithDiffLines);
+ discussion.truncated_diff_lines = diffLines;
},
};
diff --git a/app/assets/javascripts/pages/admin/runners/index.js b/app/assets/javascripts/pages/admin/runners/index.js
new file mode 100644
index 00000000000..ce8fd18b6a2
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/runners/index.js
@@ -0,0 +1,10 @@
+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', () => {
+ initFilteredSearch({
+ page: FILTERED_SEARCH.ADMIN_RUNNERS,
+ filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
+ });
+});
diff --git a/app/assets/javascripts/pages/constants.js b/app/assets/javascripts/pages/constants.js
index 328b6541636..5e119454ce1 100644
--- a/app/assets/javascripts/pages/constants.js
+++ b/app/assets/javascripts/pages/constants.js
@@ -3,4 +3,5 @@
export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
+ ADMIN_RUNNERS: 'admin/runners',
};
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 914f804fdd3..736c6a62610 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,11 +1,13 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index 1600faa3611..b798a254459 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -1,11 +1,13 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
isGroupDecendent: true,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index 70fdb0ef40d..23edbdc5cad 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -4,12 +4,14 @@ import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index a7aa616319f..1fad0fb7297 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -2,12 +2,14 @@ import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 69a9132a2da..cc6e620f365 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -1,7 +1,4 @@
-import {
- Vue,
- mrWidgetOptions,
-} from './dependencies';
+import { Vue, mrWidgetOptions } from './dependencies';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index dc6be025f11..b5eaaf054e7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -107,10 +107,14 @@ export default {
created() {
this.initPolling();
this.bindEventHubListeners();
+ eventHub.$on('mr.discussion.updated', this.checkStatus);
},
mounted() {
this.handleMounted();
},
+ beforeDestroy() {
+ eventHub.$off('mr.discussion.updated', this.checkStatus);
+ },
methods: {
createService(store) {
const endpoints = {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index cb29b5d4313..57539212e0c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -703,7 +703,8 @@
text-overflow: ellipsis;
.user-status-emoji {
- margin: 0 $gl-padding-8 0 $gl-padding-4;
+ margin-left: $gl-padding-4;
+ margin-right: 0;
}
}
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 090149fc1e2..911603bac17 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -1,15 +1,12 @@
class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: :index
- # rubocop: disable CodeReuse/ActiveRecord
def index
- sort = params[:sort] == 'contacted_asc' ? { contacted_at: :asc } : { id: :desc }
- @runners = Ci::Runner.order(sort)
- @runners = @runners.search(params[:search]) if params[:search].present?
- @runners = @runners.page(params[:page]).per(30)
- @active_runners_cnt = Ci::Runner.online.count
+ finder = Admin::RunnersFinder.new(params: params)
+ @runners = finder.execute
+ @active_runners_count = Ci::Runner.online.count
+ @sort = finder.sort_key
end
- # rubocop: enable CodeReuse/ActiveRecord
def show
assign_builds_and_projects
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 0469e7e1e1f..78f7f6d4e23 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -22,7 +22,7 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
private
def group_milestones
- groups = GroupsFinder.new(current_user, all_available: true).execute
+ groups = GroupsFinder.new(current_user, all_available: false).execute
DashboardGroupMilestone.build_collection(groups)
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 94e6efca487..a43d47797f1 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -96,6 +96,7 @@ class ProfilesController < Profiles::ApplicationController
:location,
:name,
:public_email,
+ :commit_email,
:skype,
:twitter,
:username,
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index b4fd09c06e5..eb0fad6cbb2 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -141,7 +141,8 @@ class Projects::ClustersController < Projects::ApplicationController
:gcp_project_id,
:zone,
:num_nodes,
- :machine_type
+ :machine_type,
+ :legacy_abac
]).merge(
provider_type: :gcp,
platform_type: :kubernetes
diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb
index b6ee49df99b..2cc8a978877 100644
--- a/app/finders/access_requests_finder.rb
+++ b/app/finders/access_requests_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AccessRequestsFinder
attr_accessor :source
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
index 6b805c432f6..e2b9b0b44c1 100644
--- a/app/finders/admin/projects_finder.rb
+++ b/app/finders/admin/projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::ProjectsFinder
attr_reader :params, :current_user
diff --git a/app/finders/admin/runners_finder.rb b/app/finders/admin/runners_finder.rb
new file mode 100644
index 00000000000..3c2d7ee7d76
--- /dev/null
+++ b/app/finders/admin/runners_finder.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class Admin::RunnersFinder < UnionFinder
+ NUMBER_OF_RUNNERS_PER_PAGE = 30
+
+ def initialize(params:)
+ @params = params
+ end
+
+ def execute
+ search!
+ filter_by_status!
+ sort!
+ paginate!
+
+ @runners
+ end
+
+ def sort_key
+ if @params[:sort] == 'contacted_asc'
+ 'contacted_asc'
+ else
+ 'created_date'
+ end
+ end
+
+ private
+
+ def search!
+ @runners =
+ if @params[:search].present?
+ Ci::Runner.search(@params[:search])
+ else
+ Ci::Runner.all
+ end
+ end
+
+ def filter_by_status!
+ status = @params[:status_status]
+ if status.present? && Ci::Runner::AVAILABLE_STATUSES.include?(status)
+ @runners = @runners.public_send(status) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+
+ def sort!
+ @runners = @runners.order_by(sort_key)
+ end
+
+ def paginate!
+ @runners = @runners.page(@params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
+ end
+end
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 8bb1366867c..970efa79dfb 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BranchesFinder
def initialize(repository, params = {})
@repository = repository
diff --git a/app/finders/clusters_finder.rb b/app/finders/clusters_finder.rb
index c13f98257bf..b40d6c41b71 100644
--- a/app/finders/clusters_finder.rb
+++ b/app/finders/clusters_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ClustersFinder
def initialize(project, user, scope)
@project = project
diff --git a/app/finders/concerns/created_at_filter.rb b/app/finders/concerns/created_at_filter.rb
index ac9ac77732c..6b5863a5c53 100644
--- a/app/finders/concerns/created_at_filter.rb
+++ b/app/finders/concerns/created_at_filter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CreatedAtFilter
def by_created_at(items)
items = items.created_before(params[:created_before]) if params[:created_before].present?
diff --git a/app/finders/concerns/custom_attributes_filter.rb b/app/finders/concerns/custom_attributes_filter.rb
index 022a19b0a7f..825c3a6b5b7 100644
--- a/app/finders/concerns/custom_attributes_filter.rb
+++ b/app/finders/concerns/custom_attributes_filter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CustomAttributesFilter
# rubocop: disable CodeReuse/ActiveRecord
def by_custom_attributes(items)
diff --git a/app/finders/concerns/finder_methods.rb b/app/finders/concerns/finder_methods.rb
index ce0ec214b8a..5290313585f 100644
--- a/app/finders/concerns/finder_methods.rb
+++ b/app/finders/concerns/finder_methods.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FinderMethods
# rubocop: disable CodeReuse/ActiveRecord
def find_by!(*args)
diff --git a/app/finders/concerns/finder_with_cross_project_access.rb b/app/finders/concerns/finder_with_cross_project_access.rb
index f70a0b60864..e038636f0c4 100644
--- a/app/finders/concerns/finder_with_cross_project_access.rb
+++ b/app/finders/concerns/finder_with_cross_project_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Module to prepend into finders to specify wether or not the finder requires
# cross project access
#
diff --git a/app/finders/contributed_projects_finder.rb b/app/finders/contributed_projects_finder.rb
index 1447def0cdf..c1ef9dfefa7 100644
--- a/app/finders/contributed_projects_finder.rb
+++ b/app/finders/contributed_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ContributedProjectsFinder < UnionFinder
def initialize(user)
@user = user
diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb
index 95af6214c7d..419be46fafe 100644
--- a/app/finders/environments_finder.rb
+++ b/app/finders/environments_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EnvironmentsFinder
attr_reader :project, :current_user, :params
diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb
index a8d4f56a84c..fd7aeca0d8b 100644
--- a/app/finders/events_finder.rb
+++ b/app/finders/events_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EventsFinder
prepend FinderMethods
prepend FinderWithCrossProjectAccess
diff --git a/app/finders/fork_projects_finder.rb b/app/finders/fork_projects_finder.rb
index 8713d43136f..03ace7e8057 100644
--- a/app/finders/fork_projects_finder.rb
+++ b/app/finders/fork_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ForkProjectsFinder < ProjectsFinder
# rubocop: disable CodeReuse/ActiveRecord
def initialize(project, params: {}, current_user: nil)
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 15060a99930..9d57d2d3bc9 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# GroupDescendantsFinder
#
# Used to find and filter all subgroups and projects of a passed parent group
diff --git a/app/finders/group_finder.rb b/app/finders/group_finder.rb
index 8fde3a00af7..d2ad8a372b1 100644
--- a/app/finders/group_finder.rb
+++ b/app/finders/group_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GroupFinder
include Gitlab::Allowable
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index cab1ff8fb98..eebc67cfa9e 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GroupMembersFinder
def initialize(group)
@group = group
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index 52a347fa598..4155b6af8da 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# GroupProjectsFinder
#
# Used to filter Projects by set of params
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 436abb332ff..a35a3ed6142 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# GroupsFinder
#
# Used to filter Groups by a set of params
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 3308fd6d697..251a559878a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# IssuableFinder
#
# Used to filter Issues and MergeRequests collections by set of params
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index c347ef1ec19..770e0bfe1a3 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Finders::Issues class
#
# Used to filter Issues collections by set of params
diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb
index 47174980258..18cc6891ca4 100644
--- a/app/finders/joined_groups_finder.rb
+++ b/app/finders/joined_groups_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JoinedGroupsFinder < UnionFinder
def initialize(user)
@user = user
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 17c55f8db50..08fc2968e77 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class LabelsFinder < UnionFinder
prepend FinderWithCrossProjectAccess
include FinderMethods
diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb
index fad33f0eca2..196922709f7 100644
--- a/app/finders/license_template_finder.rb
+++ b/app/finders/license_template_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# LicenseTemplateFinder
#
# Used to find license templates, which may come from a variety of external
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index 58e3606692d..48777838d60 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MembersFinder
attr_reader :project, :current_user, :group
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb
index 66d4c4018f3..5f0589f6c8b 100644
--- a/app/finders/merge_request_target_project_finder.rb
+++ b/app/finders/merge_request_target_project_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MergeRequestTargetProjectFinder
include FinderMethods
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index c6403f8f25d..b698a3c7b09 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Finders::MergeRequest class
#
# Used to filter MergeRequests collections by set of params
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index 7dc2a5b0b66..47231ea80c7 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Search for milestones
#
# params - Hash
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 580984329a1..c67c2065440 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NotesFinder
FETCH_OVERLAP = 5.seconds
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
index 6b610ffc687..5beea92689f 100644
--- a/app/finders/personal_access_tokens_finder.rb
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PersonalAccessTokensFinder
attr_accessor :params
diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb
index 16e4e060786..20f5b221a89 100644
--- a/app/finders/personal_projects_finder.rb
+++ b/app/finders/personal_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PersonalProjectsFinder < UnionFinder
include Gitlab::Allowable
diff --git a/app/finders/pipeline_schedules_finder.rb b/app/finders/pipeline_schedules_finder.rb
index d131e1659c1..3beee608268 100644
--- a/app/finders/pipeline_schedules_finder.rb
+++ b/app/finders/pipeline_schedules_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelineSchedulesFinder
attr_reader :project, :pipeline_schedules
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index b03d730f975..3d0d3219a94 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelinesFinder
attr_reader :project, :pipelines, :params, :current_user
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 0f2fdf317a4..c2404412006 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# ProjectsFinder
#
# Used to filter Projects by set of params
diff --git a/app/finders/runner_jobs_finder.rb b/app/finders/runner_jobs_finder.rb
index d4f2474197f..4fca4ec94f3 100644
--- a/app/finders/runner_jobs_finder.rb
+++ b/app/finders/runner_jobs_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RunnerJobsFinder
attr_reader :runner, :params
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index 32da3e8f404..715dffb972f 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Snippets Finder
#
# Used to filter Snippets collections by a set of params
diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb
index b474f0805dc..2ffd46245e9 100644
--- a/app/finders/tags_finder.rb
+++ b/app/finders/tags_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TagsFinder
def initialize(repository, params)
@repository = repository
diff --git a/app/finders/template_finder.rb b/app/finders/template_finder.rb
index 7cdc7a32acc..c92ee9ca9ac 100644
--- a/app/finders/template_finder.rb
+++ b/app/finders/template_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TemplateFinder
VENDORED_TEMPLATES = {
dockerfiles: ::Gitlab::Template::DockerfileTemplate,
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 6481893a195..0d9f16fdce7 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# TodosFinder
#
# Used to filter Todos by set of params
diff --git a/app/finders/union_finder.rb b/app/finders/union_finder.rb
index 91b845871a8..798c3eba0f3 100644
--- a/app/finders/union_finder.rb
+++ b/app/finders/union_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class UnionFinder
# rubocop: disable CodeReuse/ActiveRecord
def find_union(segments, klass)
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 7405d6d27d9..a4daf5b5841 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Get user activity feed for projects common for a user and a logged in user
#
# - current_user: The user viewing the events
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index 46672301851..f2ad9b4bda5 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# UsersFinder
#
# Used to filter users by set of params
diff --git a/app/graphql/functions/base_function.rb b/app/graphql/functions/base_function.rb
index 42fb8f99acc..2512ecbd255 100644
--- a/app/graphql/functions/base_function.rb
+++ b/app/graphql/functions/base_function.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Functions
class BaseFunction < GraphQL::Function
end
diff --git a/app/graphql/functions/echo.rb b/app/graphql/functions/echo.rb
index e5bf109b8d7..3104486faac 100644
--- a/app/graphql/functions/echo.rb
+++ b/app/graphql/functions/echo.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Functions
class Echo < BaseFunction
argument :text, GraphQL::STRING_TYPE
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 8755a1a62e7..06d26309b5b 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GitlabSchema < GraphQL::Schema
use BatchLoader::GraphQL
use Gitlab::Graphql::Authorize
diff --git a/app/graphql/mutations/concerns/mutations/resolves_project.rb b/app/graphql/mutations/concerns/mutations/resolves_project.rb
index 0dd1f264a52..da9814e88b0 100644
--- a/app/graphql/mutations/concerns/mutations/resolves_project.rb
+++ b/app/graphql/mutations/concerns/mutations/resolves_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mutations
module ResolvesProject
extend ActiveSupport::Concern
diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb
index 2149e72e2df..54f01c99d78 100644
--- a/app/graphql/mutations/merge_requests/base.rb
+++ b/app/graphql/mutations/merge_requests/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mutations
module MergeRequests
class Base < BaseMutation
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 89b7f9dad6f..459933af9d3 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
end
diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb
index 9ec45378d8e..8fd26d85994 100644
--- a/app/graphql/resolvers/concerns/resolves_pipelines.rb
+++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ResolvesPipelines
extend ActiveSupport::Concern
diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb
index 4eb28aaed6c..8d3da33e8d2 100644
--- a/app/graphql/resolvers/full_path_resolver.rb
+++ b/app/graphql/resolvers/full_path_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
module FullPathResolver
extend ActiveSupport::Concern
diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
index 00b51ee1381..b371f1335f8 100644
--- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb
+++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class MergeRequestPipelinesResolver < BaseResolver
include ::ResolvesPipelines
diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb
index 6b7d0ce6e24..b87c95217f7 100644
--- a/app/graphql/resolvers/merge_request_resolver.rb
+++ b/app/graphql/resolvers/merge_request_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class MergeRequestResolver < BaseResolver
argument :iid, GraphQL::ID_TYPE,
diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb
index 7f175a3b26c..86094c46c2a 100644
--- a/app/graphql/resolvers/project_pipelines_resolver.rb
+++ b/app/graphql/resolvers/project_pipelines_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class ProjectPipelinesResolver < BaseResolver
include ResolvesPipelines
diff --git a/app/graphql/resolvers/project_resolver.rb b/app/graphql/resolvers/project_resolver.rb
index ec115bad896..ac7c9b0ce2e 100644
--- a/app/graphql/resolvers/project_resolver.rb
+++ b/app/graphql/resolvers/project_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class ProjectResolver < BaseResolver
prepend FullPathResolver
diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb
index b45a845f74f..cf43fea45e6 100644
--- a/app/graphql/types/base_enum.rb
+++ b/app/graphql/types/base_enum.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseEnum < GraphQL::Schema::Enum
end
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index c5740a334d7..2b2ea64c00b 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseField < GraphQL::Schema::Field
prepend Gitlab::Graphql::Authorize
diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb
index 309e336e6c8..aebed035d3b 100644
--- a/app/graphql/types/base_input_object.rb
+++ b/app/graphql/types/base_input_object.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseInputObject < GraphQL::Schema::InputObject
end
diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb
index 69e72dc5808..3451a195c33 100644
--- a/app/graphql/types/base_interface.rb
+++ b/app/graphql/types/base_interface.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module BaseInterface
include GraphQL::Schema::Interface
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index 754adf4c04d..82b78abd573 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseObject < GraphQL::Schema::Object
prepend Gitlab::Graphql::Present
diff --git a/app/graphql/types/base_scalar.rb b/app/graphql/types/base_scalar.rb
index c0aa38be239..719bc808f47 100644
--- a/app/graphql/types/base_scalar.rb
+++ b/app/graphql/types/base_scalar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseScalar < GraphQL::Schema::Scalar
end
diff --git a/app/graphql/types/base_union.rb b/app/graphql/types/base_union.rb
index 36337fc6ee5..30a5668c0bb 100644
--- a/app/graphql/types/base_union.rb
+++ b/app/graphql/types/base_union.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseUnion < GraphQL::Schema::Union
end
diff --git a/app/graphql/types/ci/pipeline_status_enum.rb b/app/graphql/types/ci/pipeline_status_enum.rb
index 2c12e5001d8..c19ddf5bb25 100644
--- a/app/graphql/types/ci/pipeline_status_enum.rb
+++ b/app/graphql/types/ci/pipeline_status_enum.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module Ci
class PipelineStatusEnum < BaseEnum
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index bbb7d9354d0..2bbffad4563 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module Ci
class PipelineType < BaseObject
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 88cd2adc6dc..fb740b6fb1c 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class MergeRequestType < BaseObject
expose_permissions Types::PermissionTypes::MergeRequest
diff --git a/app/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb
index 934ed572e56..26a71e2bfbb 100644
--- a/app/graphql/types/permission_types/base_permission_type.rb
+++ b/app/graphql/types/permission_types/base_permission_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
class BasePermissionType < BaseObject
diff --git a/app/graphql/types/permission_types/ci/pipeline.rb b/app/graphql/types/permission_types/ci/pipeline.rb
index 942539c7cf7..73e44a33eba 100644
--- a/app/graphql/types/permission_types/ci/pipeline.rb
+++ b/app/graphql/types/permission_types/ci/pipeline.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
module Ci
diff --git a/app/graphql/types/permission_types/merge_request.rb b/app/graphql/types/permission_types/merge_request.rb
index 5c21f6ee9c6..13995d3ea8f 100644
--- a/app/graphql/types/permission_types/merge_request.rb
+++ b/app/graphql/types/permission_types/merge_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
class MergeRequest < BasePermissionType
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index 755699a4415..066ce64a254 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
class Project < BasePermissionType
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 97707215b4e..7b879608b34 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class ProjectType < BaseObject
expose_permissions Types::PermissionTypes::Project
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 010ec2d7942..7c41716b82a 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class QueryType < BaseObject
graphql_name 'Query'
diff --git a/app/graphql/types/time_type.rb b/app/graphql/types/time_type.rb
index 2333d82ad1e..f045a50e672 100644
--- a/app/graphql/types/time_type.rb
+++ b/app/graphql/types/time_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class TimeType < BaseScalar
graphql_name 'Time'
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 1f67c045d1c..56f6686da57 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -191,7 +191,7 @@ module IssuablesHelper
output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))
- output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block")
+ output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block prepend-left-8")
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
output.join.html_safe
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index f2cd676bb1b..0d638b850b4 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -74,14 +74,21 @@ module MarkupHelper
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(object, attribute, max_chars = nil, options = {})
md = markdown_field(object, attribute, options)
+ return nil unless md.present?
- text = truncate_visible(md, max_chars || md.length) if md.present?
+ tags = %w(a gl-emoji b pre code p span)
+ tags << 'img' if options[:allow_images]
- sanitize(
+ text = truncate_visible(md, max_chars || md.length)
+ text = sanitize(
text,
- tags: %w(a img gl-emoji b pre code p span),
+ tags: tags,
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
)
+
+ # since <img> tags are stripped, this can leave empty <a> tags hanging around
+ # (as our markdown wraps images in links)
+ options[:allow_images] ? text : strip_empty_link_tags(text).html_safe
end
def markdown(text, context = {})
@@ -235,6 +242,16 @@ module MarkupHelper
end
end
+ def strip_empty_link_tags(text)
+ scrubber = Loofah::Scrubber.new do |node|
+ node.remove if node.name == 'a' && node.content.blank?
+ end
+
+ # Use `Loofah` directly instead of `sanitize`
+ # as we still use the `rails-deprecated_sanitizer` gem
+ Loofah.fragment(text).scrub!(scrubber).to_s
+ end
+
def markdown_toolbar_button(options = {})
data = options[:data].merge({ container: 'body' })
content_tag :button,
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index a6e65d30eda..0c59bdd6abb 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -24,7 +24,8 @@ module SortingHelper
sort_value_recently_updated => sort_title_recently_updated,
sort_value_popularity => sort_title_popularity,
sort_value_priority => sort_title_priority,
- sort_value_upvotes => sort_title_upvotes
+ sort_value_upvotes => sort_title_upvotes,
+ sort_value_contacted_date => sort_title_contacted_date
}
end
@@ -241,6 +242,10 @@ module SortingHelper
s_('SortOptions|Most popular')
end
+ def sort_title_contacted_date
+ s_('SortOptions|Last Contact')
+ end
+
# Values.
def sort_value_access_level_asc
'access_level_asc'
@@ -361,4 +366,8 @@ module SortingHelper
def sort_value_upvotes
'upvotes_desc'
end
+
+ def sort_value_contacted_date
+ 'contacted_asc'
+ end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index f41955f43e7..eabb41c29d7 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -11,7 +11,9 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour
UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
- AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
+ AVAILABLE_TYPES = %w[specific shared].freeze
+ AVAILABLE_STATUSES = %w[active paused online offline].freeze
+ AVAILABLE_SCOPES = (AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
ignore_column :is_shared
@@ -29,6 +31,13 @@ module Ci
scope :active, -> { where(active: true) }
scope :paused, -> { where(active: false) }
scope :online, -> { where('contacted_at > ?', contact_time_deadline) }
+ # The following query using negation is cheaper than using `contacted_at <= ?`
+ # because there are less runners online than have been created. The
+ # resulting query is quickly finding online ones and then uses the regular
+ # indexed search and rejects the ones that are in the previous set. If we
+ # did `contacted_at <= ?` the query would effectively have to do a seq
+ # scan.
+ scope :offline, -> { where.not(id: online) }
scope :ordered, -> { order(id: :desc) }
# BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb`
@@ -63,6 +72,9 @@ module Ci
.project_type
end
+ scope :order_contacted_at_asc, -> { order(contacted_at: :asc) }
+ scope :order_created_at_desc, -> { order(created_at: :desc) }
+
validate :tag_constraints
validates :access_level, presence: true
validates :runner_type, presence: true
@@ -115,6 +127,14 @@ module Ci
ONLINE_CONTACT_TIMEOUT.ago
end
+ def self.order_by(order)
+ if order == 'contacted_asc'
+ order_contacted_at_asc
+ else
+ order_created_at_desc
+ end
+ end
+
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 2371b0237d8..3d84eeed5a8 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -73,19 +73,10 @@ module Clusters
"clientSecret" => oauth_application.secret,
"callbackUrl" => callback_url
}
- },
- "singleuser" => {
- "extraEnv" => {
- "GITLAB_PROJECT_ID" => project_id
- }
}
}
end
- def project_id
- cluster&.project&.id
- end
-
def gitlab_url
Gitlab.config.gitlab.url
end
diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb
index 744f7f48dc8..58761fce952 100644
--- a/app/models/concerns/protected_branch_access.rb
+++ b/app/models/concerns/protected_branch_access.rb
@@ -2,18 +2,17 @@
module ProtectedBranchAccess
extend ActiveSupport::Concern
+ include ProtectedRefAccess
included do
- include ProtectedRefAccess
-
belongs_to :protected_branch
delegate :project, to: :protected_branch
+ end
- def check_access(user)
- return false if access_level == Gitlab::Access::NO_ACCESS
+ def check_access(user)
+ return false if access_level == Gitlab::Access::NO_ACCESS
- super
- end
+ super
end
end
diff --git a/app/models/concerns/protected_tag_access.rb b/app/models/concerns/protected_tag_access.rb
index 04bd54d6b1c..3f5696c0749 100644
--- a/app/models/concerns/protected_tag_access.rb
+++ b/app/models/concerns/protected_tag_access.rb
@@ -2,10 +2,9 @@
module ProtectedTagAccess
extend ActiveSupport::Concern
+ include ProtectedRefAccess
included do
- include ProtectedRefAccess
-
belongs_to :protected_tag
delegate :project, to: :protected_tag
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index 4e39f60b0d2..067e14dda1c 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -13,7 +13,7 @@ class DashboardGroupMilestone < GlobalMilestone
end
def self.build_collection(groups)
- MilestonesFinder.new(group_ids: groups.pluck(:id)).execute.map { |m| new(m) } # rubocop: disable CodeReuse/Finder
+ MilestonesFinder.new(group_ids: groups.select(:id)).execute.map { |m| new(m) } # rubocop: disable CodeReuse/Finder
end
override :group_milestone?
diff --git a/app/models/user.rb b/app/models/user.rb
index dac8779488d..d68108a8e8e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -161,6 +161,7 @@ class User < ActiveRecord::Base
validates :notification_email, presence: true
validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
+ validates :commit_email, email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit,
presence: true,
@@ -173,12 +174,15 @@ class User < ActiveRecord::Base
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
+ validate :owns_commit_email, if: :commit_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
+ before_validation :set_commit_email, if: :commit_email_changed?
before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
+ before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped
before_save :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
@@ -619,6 +623,32 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
+ def owns_commit_email
+ return if read_attribute(:commit_email).blank?
+
+ errors.add(:commit_email, "is not an email you own") unless verified_emails.include?(commit_email)
+ end
+
+ # Define commit_email-related attribute methods explicitly instead of relying
+ # on ActiveRecord to provide them. Some of the specs use the current state of
+ # the model code but an older database schema, so we need to guard against the
+ # possibility of the commit_email column not existing.
+
+ def commit_email
+ return unless has_attribute?(:commit_email)
+
+ # The commit email is the same as the primary email if undefined
+ super.presence || self.email
+ end
+
+ def commit_email=(email)
+ super if has_attribute?(:commit_email)
+ end
+
+ def commit_email_changed?
+ has_attribute?(:commit_email) && super
+ end
+
# see if the new email is already a verified secondary email
def check_for_verified_email
skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
@@ -873,10 +903,17 @@ class User < ActiveRecord::Base
end
end
+ def set_commit_email
+ if commit_email.blank? || verified_emails.exclude?(commit_email)
+ self.commit_email = nil
+ end
+ end
+
def update_secondary_emails!
set_notification_email
set_public_email
- save if notification_email_changed? || public_email_changed?
+ set_commit_email
+ save if notification_email_changed? || public_email_changed? || commit_email_changed?
end
def set_projects_limit
diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb
deleted file mode 100644
index 15eda56cac6..00000000000
--- a/app/services/ci/fetch_kubernetes_token_service.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-##
-# TODO:
-# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb
-# We should dry up those classes not to repeat the same code.
-# Maybe we should have a special facility (e.g. lib/kubernetes_api) to maintain all Kubernetes API caller.
-module Ci
- class FetchKubernetesTokenService
- attr_reader :api_url, :ca_pem, :username, :password
-
- def initialize(api_url, ca_pem, username, password)
- @api_url = api_url
- @ca_pem = ca_pem
- @username = username
- @password = password
- end
-
- def execute
- read_secrets.each do |secret|
- name = secret.dig('metadata', 'name')
- if /default-token/ =~ name
- token_base64 = secret.dig('data', 'token')
- return Base64.decode64(token_base64) if token_base64
- end
- end
-
- nil
- end
-
- private
-
- def read_secrets
- kubeclient = build_kubeclient!
-
- kubeclient.get_secrets.as_json
- rescue Kubeclient::HttpError => err
- raise err unless err.error_code == 404
-
- []
- end
-
- def build_kubeclient!(api_path: 'api', api_version: 'v1')
- raise "Incomplete settings" unless api_url && username && password
-
- ::Kubeclient::Client.new(
- join_api_url(api_path),
- api_version,
- auth_options: { username: username, password: password },
- ssl_options: kubeclient_ssl_options,
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def join_api_url(api_path)
- url = URI.parse(api_url)
- prefix = url.path.sub(%r{/+\z}, '')
-
- url.path = [prefix, api_path].join("/")
-
- url.to_s
- end
-
- def kubeclient_ssl_options
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
- end
-end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 264419501dc..3ae0a4a19d0 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -9,17 +9,24 @@ module Clusters
@provider = provider
configure_provider
+ create_gitlab_service_account!
configure_kubernetes
cluster.save!
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ rescue Kubeclient::HttpError => e
+ provider.make_errored!("Failed to run Kubeclient: #{e.message}")
rescue ActiveRecord::RecordInvalid => e
provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}")
end
private
+ def create_gitlab_service_account!
+ Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute
+ end
+
def configure_provider
provider.endpoint = gke_cluster.endpoint
provider.status_event = :make_created
@@ -32,15 +39,54 @@ module Clusters
ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
username: gke_cluster.master_auth.username,
password: gke_cluster.master_auth.password,
+ authorization_type: authorization_type,
token: request_kubernetes_token)
end
def request_kubernetes_token
- Ci::FetchKubernetesTokenService.new(
+ Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute
+ end
+
+ def authorization_type
+ create_rbac_cluster? ? 'rbac' : 'abac'
+ end
+
+ def create_rbac_cluster?
+ !provider.legacy_abac?
+ end
+
+ def kube_client
+ @kube_client ||= build_kube_client!(
'https://' + gke_cluster.endpoint,
Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
gke_cluster.master_auth.username,
- gke_cluster.master_auth.password).execute
+ gke_cluster.master_auth.password,
+ api_groups: ['api', 'apis/rbac.authorization.k8s.io']
+ )
+ end
+
+ def build_kube_client!(api_url, ca_pem, username, password, api_groups: ['api'], api_version: 'v1')
+ raise "Incomplete settings" unless api_url && username && password
+
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ api_groups,
+ api_version,
+ auth_options: { username: username, password: password },
+ ssl_options: kubeclient_ssl_options(ca_pem),
+ http_proxy_uri: ENV['http_proxy']
+ )
+ end
+
+ def kubeclient_ssl_options(ca_pem)
+ opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
+
+ if ca_pem.present?
+ opts[:cert_store] = OpenSSL::X509::Store.new
+ opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
+ end
+
+ opts
end
def gke_cluster
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
new file mode 100644
index 00000000000..d014d73b3e8
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ SERVICE_ACCOUNT_NAME = 'gitlab'
+ SERVICE_ACCOUNT_NAMESPACE = 'default'
+ SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token'
+ CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
+ CLUSTER_ROLE_NAME = 'cluster-admin'
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
new file mode 100644
index 00000000000..d17744591e6
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class CreateServiceAccountService
+ attr_reader :kubeclient, :rbac
+
+ def initialize(kubeclient, rbac:)
+ @kubeclient = kubeclient
+ @rbac = rbac
+ end
+
+ def execute
+ kubeclient.create_service_account(service_account_resource)
+ kubeclient.create_secret(service_account_token_resource)
+ kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac
+ end
+
+ private
+
+ def service_account_resource
+ Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate
+ end
+
+ def service_account_token_resource
+ Gitlab::Kubernetes::ServiceAccountToken.new(
+ SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate
+ end
+
+ def cluster_role_binding_resource
+ subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
+
+ Gitlab::Kubernetes::ClusterRoleBinding.new(
+ CLUSTER_ROLE_BINDING_NAME,
+ CLUSTER_ROLE_NAME,
+ subjects
+ ).generate
+ end
+
+ def service_account_name
+ SERVICE_ACCOUNT_NAME
+ end
+
+ def service_account_namespace
+ SERVICE_ACCOUNT_NAMESPACE
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
new file mode 100644
index 00000000000..9e09345c8dc
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class FetchKubernetesTokenService
+ attr_reader :kubeclient
+
+ def initialize(kubeclient)
+ @kubeclient = kubeclient
+ end
+
+ def execute
+ token_base64 = get_secret&.dig('data', 'token')
+ Base64.decode64(token_base64) if token_base64
+ end
+
+ private
+
+ def get_secret
+ kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json
+ rescue Kubeclient::HttpError => err
+ raise err unless err.error_code == 404
+
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb
index ab1bf9c64f6..80040511ec2 100644
--- a/app/services/clusters/gcp/provision_service.rb
+++ b/app/services/clusters/gcp/provision_service.rb
@@ -27,7 +27,9 @@ module Clusters
provider.zone,
provider.cluster.name,
provider.num_nodes,
- machine_type: provider.machine_type)
+ machine_type: provider.machine_type,
+ legacy_abac: provider.legacy_abac
+ )
unless operation.status == 'PENDING' || operation.status == 'RUNNING'
return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 43937b01339..9c15226f0ec 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -1,5 +1,5 @@
-%tr{ id: dom_id(runner) }
- %td
+.gl-responsive-table-row{ id: dom_id(runner) }
+ = render layout: 'runner_table_cell', locals: { label: _('Type') } do
- if runner.instance_type?
%span.badge.badge-success shared
- elsif runner.group_type?
@@ -11,41 +11,50 @@
- unless runner.active?
%span.badge.badge-danger paused
- %td
- = link_to admin_runner_path(runner) do
- = runner.short_sha
- %td
+ = render layout: 'runner_table_cell', locals: { label: _('Runner token') } do
+ = link_to runner.short_sha, admin_runner_path(runner)
+
+ = render layout: 'runner_table_cell', locals: { label: _('Description') } do
= runner.description
- %td
+
+ = render layout: 'runner_table_cell', locals: { label: _('Version') } do
= runner.version
- %td
+
+ = render layout: 'runner_table_cell', locals: { label: _('IP Address') } do
= runner.ip_address
- %td
+
+ = render layout: 'runner_table_cell', locals: { label: _('Projects') } do
- if runner.instance_type? || runner.group_type?
- n/a
+ = _('n/a')
- else
= runner.projects.count(:all)
- %td
- #{runner.builds.count(:all)}
- %td
+
+ = render layout: 'runner_table_cell', locals: { label: _('Jobs') } do
+ = runner.builds.count(:all)
+
+ = render layout: 'runner_table_cell', locals: { label: _('Tags') } do
- runner.tag_list.sort.each do |tag|
%span.badge.badge-primary
= tag
- %td
+
+ = render layout: 'runner_table_cell', locals: { label: _('Last contact') } do
- if runner.contacted_at
= time_ago_with_tooltip runner.contacted_at
- else
- Never
- %td.admin-runner-btn-group-cell
- .float-right.btn-group
- = link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do
- = icon('pencil')
- &nbsp;
- - if runner.active?
- = link_to [:pause, :admin, runner], method: :get, class: 'btn btn-sm 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, :admin, runner], method: :get, class: 'btn btn-default btn-sm has-tooltip', title: 'Resume', ref: 'tooltip', aria: { label: 'Resume' }, data: { placement: 'top', container: 'body'} do
- = icon('play')
- = link_to [:admin, runner], method: :delete, class: 'btn btn-danger btn-sm has-tooltip', title: 'Remove', ref: 'tooltip', aria: { label: 'Remove' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
- = icon('remove')
+ = _('Never')
+
+ .table-section.table-button-footer.section-10
+ .btn-group.table-action-buttons
+ .btn-group
+ = link_to admin_runner_path(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, :admin, runner], method: :get, 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, :admin, runner], method: :get, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
+ = icon('play')
+ .btn-group
+ = link_to [:admin, 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')
diff --git a/app/views/admin/runners/_runner_table_cell.html.haml b/app/views/admin/runners/_runner_table_cell.html.haml
new file mode 100644
index 00000000000..78526ee6d23
--- /dev/null
+++ b/app/views/admin/runners/_runner_table_cell.html.haml
@@ -0,0 +1,4 @@
+.table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= label
+ .table-mobile-content
+ = yield
diff --git a/app/views/admin/runners/_sort_dropdown.html.haml b/app/views/admin/runners/_sort_dropdown.html.haml
new file mode 100644
index 00000000000..b201e6bf10e
--- /dev/null
+++ b/app/views/admin/runners/_sort_dropdown.html.haml
@@ -0,0 +1,11 @@
+- sorted_by = sort_options_hash[@sort]
+
+.dropdown.inline.prepend-left-10
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
+ = sorted_by
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
+ %li
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by)
+ = sortable_item(sort_title_contacted_date, page_filter_path(sort: sort_value_contacted_date, label: true), sorted_by)
+
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 9280ff4d478..4dc076c95c5 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,77 +1,107 @@
-- breadcrumb_title "Runners"
+- breadcrumb_title _('Runners')
- @no_container = true
%div{ class: container_class }
.bs-callout
%p
- A 'Runner' is a process which runs a job.
- You can setup as many Runners as you need.
+ = (_"A 'Runner' is a process which runs a job. You can setup as many Runners as you need.")
%br
- Runners can be placed on separate users, servers, even on your local machine.
+ = _('Runners can be placed on separate users, servers, even on your local machine.')
%br
%div
- %span Each Runner can be in one of the following states:
+ %span= _('Each Runner can be in one of the following states:')
%ul
%li
%span.badge.badge-success shared
- \- Runner runs jobs from all unassigned projects
+ \-
+ = _('Runner runs jobs from all unassigned projects')
%li
%span.badge.badge-success group
- \- Runner runs jobs from all unassigned projects in its group
+ \-
+ = _('Runner runs jobs from all unassigned projects in its group')
%li
%span.badge.badge-info specific
- \- Runner runs jobs from assigned projects
+ \-
+ = _('Runner runs jobs from assigned projects')
%li
%span.badge.badge-warning locked
- \- Runner cannot be assigned to other projects
+ \-
+ = _('Runner cannot be assigned to other projects')
%li
%span.badge.badge-danger paused
- \- Runner will not receive any new jobs
+ \-
+ = _('Runner will not receive any new jobs')
.bs-callout.clearfix
.float-left
%p
- You can reset runners registration token by pressing a button below.
+ = _('You can reset runners registration token by pressing a button below.')
.prepend-top-10
- = button_to _("Reset runners registration token"), reset_runners_token_admin_application_settings_path,
+ = button_to _('Reset runners registration token'), reset_runners_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
- data: { confirm: _("Are you sure you want to reset registration token?") }
+ data: { confirm: _('Are you sure you want to reset registration token?') }
= render partial: 'ci/runner/how_to_setup_shared_runner',
locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token }
- .append-bottom-20.clearfix
- .float-left
- = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
- .form-group
- = search_field_tag :search, params[:search], class: 'form-control input-short', placeholder: 'Runner description or token', spellcheck: false
- = submit_tag 'Search', class: 'btn'
-
- .float-right.light
- Runners currently online: #{@active_runners_cnt}
+ .bs-callout
+ %p
+ = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
- %br
+ .row-content-block.second-block
+ = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
+ .filtered-search-wrapper
+ .filtered-search-box
+ = dropdown_tag(custom_icon('icon_history'),
+ options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
+ toggle_class: 'filtered-search-history-dropdown-toggle-button',
+ dropdown_class: 'filtered-search-history-dropdown',
+ content_class: 'filtered-search-history-dropdown-content',
+ title: _('Recent searches') }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
+ = button_tag class: %w[btn btn-link] do
+ = icon('search')
+ %span
+ = _('Press Enter or click to search')
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ = button_tag class: %w[btn btn-link] do
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %i.fa{ class: "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
+ #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: %w[btn btn-link] do
+ = status.titleize
+ = button_tag class: %w[clear-search hidden] do
+ = icon('times')
+ .filter-dropdown-container
+ = render 'sort_dropdown'
- if @runners.any?
- .runners-content
+ .runners-content.content-list
.table-holder
- %table.table
- %thead
- %tr
- %th Type
- %th Runner token
- %th Description
- %th Version
- %th IP Address
- %th Projects
- %th Jobs
- %th Tags
- %th= link_to 'Last contact', admin_runners_path(safe_params.slice(:search).merge(sort: 'contacted_asc'))
- %th
+ .gl-responsive-table-row.table-row-header{ role: 'row' }
+ - [_('Type'), _('Runner token'), _('Description'), _('Version'), _('IP Address'), _('Projects'), _('Jobs'), _('Tags'), _('Last contact')].each do |label|
+ .table-section.section-10{ role: 'rowheader' }= label
- - @runners.each do |runner|
- = render "admin/runners/runner", runner: runner
- = paginate @runners, theme: "gitlab"
+ - @runners.each do |runner|
+ = render 'admin/runners/runner', runner: runner
+ = paginate @runners, theme: 'gitlab'
- else
- .nothing-here-block No runners found
+ .nothing-here-block= _('No runners found')
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 04a19ab14dd..c8faf2b3af3 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -22,7 +22,9 @@
.account-well.append-bottom-default
%ul
%li
- Your Primary Email will be used for avatar detection and web based operations, such as edits and merges.
+ Your Primary Email will be used for avatar detection.
+ %li
+ Your Commit Email will be used for web based operations, such as edits and merges.
%li
Your Notification Email will be used for account notifications.
%li
@@ -34,6 +36,8 @@
= render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? }
%span.float-right
%span.badge.badge-success Primary email
+ - if @primary_email === current_user.commit_email
+ %span.badge.badge-info Commit email
- if @primary_email === current_user.public_email
%span.badge.badge-info Public email
- if @primary_email === current_user.notification_email
@@ -42,6 +46,8 @@
%li
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.float-right
+ - if email.email === current_user.commit_email
+ %span.badge.badge-info Commit email
- if email.email === current_user.public_email
%span.badge.badge-info Public email
- if email.email === current_user.notification_email
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 0a1ee648d97..51f5ecf2166 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -91,6 +91,9 @@
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
{ help: s_("Profiles|This email will be displayed on your public profile."), include_blank: s_("Profiles|Do not show on profile") },
control_class: 'select2'
+ = f.select :commit_email, options_for_select(@user.verified_emails, selected: @user.commit_email),
+ { help: 'This email will be used for web based operations, such as edits and merges.' },
+ control_class: 'select2'
= f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
{ help: s_("Profiles|This feature is experimental and translations are not complete yet.") },
control_class: 'select2'
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml
index 9133de6559d..0222bbf7338 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/projects/clusters/gcp/_form.html.haml
@@ -61,5 +61,15 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true
+ = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
+
.form-group
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml
index 877e0cc876c..be84f2ae67c 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/projects/clusters/gcp/_show.html.haml
@@ -37,5 +37,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml
index 1f81e024ab9..f497f5b606c 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/projects/clusters/user/_form.html.haml
@@ -33,6 +33,7 @@
.form-text.text-muted
= s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
= s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
.form-group
= field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index d8580ad8ab4..ac8d58c0bfe 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -34,4 +34,4 @@
= form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4"
.col-8
.issuable-form-select-holder
- = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
+ = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date", autocomplete: 'off'