Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2c15256184
commit
0e65189f85
|
@ -45,6 +45,7 @@
|
||||||
"DevOps",
|
"DevOps",
|
||||||
"Elasticsearch",
|
"Elasticsearch",
|
||||||
"Facebook",
|
"Facebook",
|
||||||
|
"GDK",
|
||||||
"Git LFS",
|
"Git LFS",
|
||||||
"git-annex",
|
"git-annex",
|
||||||
"Git",
|
"Git",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
import * as Sentry from '@sentry/browser';
|
||||||
import {
|
import {
|
||||||
|
GlAlert,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
GlNewDropdown,
|
GlNewDropdown,
|
||||||
GlNewDropdownItem,
|
GlNewDropdownItem,
|
||||||
|
@ -19,10 +21,14 @@ export default {
|
||||||
resolved: s__('AlertManagement|Resolved'),
|
resolved: s__('AlertManagement|Resolved'),
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
|
errorMsg: s__(
|
||||||
|
'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.',
|
||||||
|
),
|
||||||
fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'),
|
fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'),
|
||||||
overviewTitle: s__('AlertManagement|Overview'),
|
overviewTitle: s__('AlertManagement|Overview'),
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
GlAlert,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
GlNewDropdown,
|
GlNewDropdown,
|
||||||
GlNewDropdownItem,
|
GlNewDropdownItem,
|
||||||
|
@ -58,20 +64,35 @@ export default {
|
||||||
update(data) {
|
update(data) {
|
||||||
return data?.project?.alertManagementAlerts?.nodes?.[0] ?? null;
|
return data?.project?.alertManagementAlerts?.nodes?.[0] ?? null;
|
||||||
},
|
},
|
||||||
|
error(error) {
|
||||||
|
this.errored = true;
|
||||||
|
Sentry.captureException(error);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { alert: null };
|
return { alert: null, errored: false, isErrorDismissed: false };
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
loading() {
|
loading() {
|
||||||
return this.$apollo.queries.alert.loading;
|
return this.$apollo.queries.alert.loading;
|
||||||
},
|
},
|
||||||
|
showErrorMsg() {
|
||||||
|
return this.errored && !this.isErrorDismissed;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
dismissError() {
|
||||||
|
this.isErrorDismissed = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
|
||||||
|
{{ $options.i18n.errorMsg }}
|
||||||
|
</gl-alert>
|
||||||
<div v-if="loading"><gl-loading-icon size="lg" class="mt-3" /></div>
|
<div v-if="loading"><gl-loading-icon size="lg" class="mt-3" /></div>
|
||||||
<div
|
<div
|
||||||
v-if="alert"
|
v-if="alert"
|
||||||
|
|
|
@ -46,6 +46,7 @@ export default {
|
||||||
'isGroup',
|
'isGroup',
|
||||||
'maskableRegex',
|
'maskableRegex',
|
||||||
'selectedEnvironment',
|
'selectedEnvironment',
|
||||||
|
'isProtectedByDefault',
|
||||||
]),
|
]),
|
||||||
canSubmit() {
|
canSubmit() {
|
||||||
return (
|
return (
|
||||||
|
@ -123,6 +124,7 @@ export default {
|
||||||
'addWildCardScope',
|
'addWildCardScope',
|
||||||
'resetSelectedEnvironment',
|
'resetSelectedEnvironment',
|
||||||
'setSelectedEnvironment',
|
'setSelectedEnvironment',
|
||||||
|
'setVariableProtected',
|
||||||
]),
|
]),
|
||||||
deleteVarAndClose() {
|
deleteVarAndClose() {
|
||||||
this.deleteVariable(this.variableBeingEdited);
|
this.deleteVariable(this.variableBeingEdited);
|
||||||
|
@ -147,6 +149,11 @@ export default {
|
||||||
}
|
}
|
||||||
this.hideModal();
|
this.hideModal();
|
||||||
},
|
},
|
||||||
|
setVariableProtectedByDefault() {
|
||||||
|
if (this.isProtectedByDefault && !this.variableBeingEdited) {
|
||||||
|
this.setVariableProtected();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -159,6 +166,7 @@ export default {
|
||||||
static
|
static
|
||||||
lazy
|
lazy
|
||||||
@hidden="resetModalHandler"
|
@hidden="resetModalHandler"
|
||||||
|
@shown="setVariableProtectedByDefault"
|
||||||
>
|
>
|
||||||
<form>
|
<form>
|
||||||
<ci-key-field
|
<ci-key-field
|
||||||
|
|
|
@ -5,14 +5,16 @@ import { parseBoolean } from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const el = document.getElementById('js-ci-project-variables');
|
const el = document.getElementById('js-ci-project-variables');
|
||||||
const { endpoint, projectId, group, maskableRegex } = el.dataset;
|
const { endpoint, projectId, group, maskableRegex, protectedByDefault } = el.dataset;
|
||||||
const isGroup = parseBoolean(group);
|
const isGroup = parseBoolean(group);
|
||||||
|
const isProtectedByDefault = parseBoolean(protectedByDefault);
|
||||||
|
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
endpoint,
|
endpoint,
|
||||||
projectId,
|
projectId,
|
||||||
isGroup,
|
isGroup,
|
||||||
maskableRegex,
|
maskableRegex,
|
||||||
|
isProtectedByDefault,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
|
|
|
@ -20,6 +20,10 @@ export const resetEditing = ({ commit, dispatch }) => {
|
||||||
commit(types.RESET_EDITING);
|
commit(types.RESET_EDITING);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setVariableProtected = ({ commit }) => {
|
||||||
|
commit(types.SET_VARIABLE_PROTECTED);
|
||||||
|
};
|
||||||
|
|
||||||
export const requestAddVariable = ({ commit }) => {
|
export const requestAddVariable = ({ commit }) => {
|
||||||
commit(types.REQUEST_ADD_VARIABLE);
|
commit(types.REQUEST_ADD_VARIABLE);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ export const TOGGLE_VALUES = 'TOGGLE_VALUES';
|
||||||
export const VARIABLE_BEING_EDITED = 'VARIABLE_BEING_EDITED';
|
export const VARIABLE_BEING_EDITED = 'VARIABLE_BEING_EDITED';
|
||||||
export const RESET_EDITING = 'RESET_EDITING';
|
export const RESET_EDITING = 'RESET_EDITING';
|
||||||
export const CLEAR_MODAL = 'CLEAR_MODAL';
|
export const CLEAR_MODAL = 'CLEAR_MODAL';
|
||||||
|
export const SET_VARIABLE_PROTECTED = 'SET_VARIABLE_PROTECTED';
|
||||||
|
|
||||||
export const REQUEST_VARIABLES = 'REQUEST_VARIABLES';
|
export const REQUEST_VARIABLES = 'REQUEST_VARIABLES';
|
||||||
export const RECEIVE_VARIABLES_SUCCESS = 'RECEIVE_VARIABLES_SUCCESS';
|
export const RECEIVE_VARIABLES_SUCCESS = 'RECEIVE_VARIABLES_SUCCESS';
|
||||||
|
|
|
@ -104,4 +104,8 @@ export default {
|
||||||
[types.SET_SELECTED_ENVIRONMENT](state, environment) {
|
[types.SET_SELECTED_ENVIRONMENT](state, environment) {
|
||||||
state.selectedEnvironment = environment;
|
state.selectedEnvironment = environment;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[types.SET_VARIABLE_PROTECTED](state) {
|
||||||
|
state.variable.protected = true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default () => ({
|
||||||
projectId: null,
|
projectId: null,
|
||||||
isGroup: null,
|
isGroup: null,
|
||||||
maskableRegex: null,
|
maskableRegex: null,
|
||||||
|
isProtectedByDefault: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
variable: {
|
variable: {
|
||||||
|
|
|
@ -3,7 +3,16 @@ import mitt from 'mitt';
|
||||||
export default () => {
|
export default () => {
|
||||||
const emitter = mitt();
|
const emitter = mitt();
|
||||||
|
|
||||||
|
emitter.once = (event, handler) => {
|
||||||
|
const wrappedHandler = evt => {
|
||||||
|
handler(evt);
|
||||||
|
emitter.off(event, wrappedHandler);
|
||||||
|
};
|
||||||
|
emitter.on(event, wrappedHandler);
|
||||||
|
};
|
||||||
|
|
||||||
emitter.$on = emitter.on;
|
emitter.$on = emitter.on;
|
||||||
|
emitter.$once = emitter.once;
|
||||||
emitter.$off = emitter.off;
|
emitter.$off = emitter.off;
|
||||||
emitter.$emit = emitter.emit;
|
emitter.$emit = emitter.emit;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script>
|
||||||
|
import { GlAlert } from '@gitlab/ui';
|
||||||
|
import getJiraImportDetailsQuery from '~/jira_import/queries/get_jira_import_details.query.graphql';
|
||||||
|
import { isInProgress } from '~/jira_import/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'IssuableListRoot',
|
||||||
|
components: {
|
||||||
|
GlAlert,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
canEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isJiraConfigured: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
projectPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isAlertShowing: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
jiraImport: {
|
||||||
|
query: getJiraImportDetailsQuery,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
fullPath: this.projectPath,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
update: ({ project }) => ({
|
||||||
|
isInProgress: isInProgress(project.jiraImportStatus),
|
||||||
|
}),
|
||||||
|
skip() {
|
||||||
|
return !this.isJiraConfigured || !this.canEdit;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
shouldShowAlert() {
|
||||||
|
return this.isAlertShowing && this.jiraImport?.isInProgress;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hideAlert() {
|
||||||
|
this.isAlertShowing = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<gl-alert v-if="shouldShowAlert" @dismiss="hideAlert">
|
||||||
|
{{ __('Import in progress. Refresh page to see newly added issues.') }}
|
||||||
|
</gl-alert>
|
||||||
|
</template>
|
|
@ -1,24 +1,62 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import createDefaultClient from '~/lib/graphql';
|
||||||
|
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||||
|
import IssuableListRootApp from './components/issuable_list_root_app.vue';
|
||||||
import IssuablesListApp from './components/issuables_list_app.vue';
|
import IssuablesListApp from './components/issuables_list_app.vue';
|
||||||
|
|
||||||
export default function initIssuablesList() {
|
function mountIssuableListRootApp() {
|
||||||
if (!gon.features || !gon.features.vueIssuablesList) {
|
const el = document.querySelector('.js-projects-issues-root');
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
const defaultClient = createDefaultClient();
|
||||||
|
const apolloProvider = new VueApollo({
|
||||||
|
defaultClient,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Vue({
|
||||||
|
el,
|
||||||
|
apolloProvider,
|
||||||
|
render(createComponent) {
|
||||||
|
return createComponent(IssuableListRootApp, {
|
||||||
|
props: {
|
||||||
|
canEdit: parseBoolean(el.dataset.canEdit),
|
||||||
|
isJiraConfigured: parseBoolean(el.dataset.isJiraConfigured),
|
||||||
|
projectPath: el.dataset.projectPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountIssuablesListApp() {
|
||||||
|
if (!gon.features?.vueIssuablesList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.js-issuables-list').forEach(el => {
|
document.querySelectorAll('.js-issuables-list').forEach(el => {
|
||||||
const { canBulkEdit, ...data } = el.dataset;
|
const { canBulkEdit, ...data } = el.dataset;
|
||||||
|
|
||||||
const props = {
|
|
||||||
...data,
|
|
||||||
canBulkEdit: Boolean(canBulkEdit),
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
el,
|
el,
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement(IssuablesListApp, { props });
|
return createElement(IssuablesListApp, {
|
||||||
|
props: {
|
||||||
|
...data,
|
||||||
|
canBulkEdit: Boolean(canBulkEdit),
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function initIssuablesList() {
|
||||||
|
mountIssuableListRootApp();
|
||||||
|
mountIssuablesListApp();
|
||||||
|
}
|
||||||
|
|
|
@ -295,7 +295,7 @@ export default {
|
||||||
.then(res => res.data)
|
.then(res => res.data)
|
||||||
.then(data => this.checkForSpam(data))
|
.then(data => this.checkForSpam(data))
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (window.location.pathname !== data.web_url) {
|
if (!window.location.pathname.includes(data.web_url)) {
|
||||||
visitUrl(data.web_url);
|
visitUrl(data.web_url);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import UsersSelect from '~/users_select';
|
||||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||||
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
|
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
|
||||||
|
import initIssuablesList from '~/issuables_list';
|
||||||
import initManualOrdering from '~/manual_ordering';
|
import initManualOrdering from '~/manual_ordering';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
@ -16,9 +17,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
page: FILTERED_SEARCH.ISSUES,
|
page: FILTERED_SEARCH.ISSUES,
|
||||||
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
|
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
|
||||||
});
|
});
|
||||||
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
|
|
||||||
|
|
||||||
|
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
|
||||||
new ShortcutsNavigation();
|
new ShortcutsNavigation();
|
||||||
new UsersSelect();
|
new UsersSelect();
|
||||||
|
|
||||||
initManualOrdering();
|
initManualOrdering();
|
||||||
|
initIssuablesList();
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
skip_before_action :merge_request, only: [:index, :bulk_update]
|
skip_before_action :merge_request, only: [:index, :bulk_update]
|
||||||
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
|
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
|
||||||
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
|
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
|
||||||
before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts, :coverage_reports, :terraform_reports]
|
before_action :authorize_read_actual_head_pipeline!, only: [
|
||||||
|
:test_reports,
|
||||||
|
:exposed_artifacts,
|
||||||
|
:coverage_reports,
|
||||||
|
:terraform_reports,
|
||||||
|
:accessibility_reports
|
||||||
|
]
|
||||||
before_action :set_issuables_index, only: [:index]
|
before_action :set_issuables_index, only: [:index]
|
||||||
before_action :authenticate_user!, only: [:assign_related_issues]
|
before_action :authenticate_user!, only: [:assign_related_issues]
|
||||||
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
|
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
|
||||||
|
@ -136,6 +142,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
reports_response(@merge_request.compare_test_reports)
|
reports_response(@merge_request.compare_test_reports)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accessibility_reports
|
||||||
|
if @merge_request.has_accessibility_reports?
|
||||||
|
reports_response(@merge_request.compare_accessibility_reports)
|
||||||
|
else
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def coverage_reports
|
def coverage_reports
|
||||||
if @merge_request.has_coverage_reports?
|
if @merge_request.has_coverage_reports?
|
||||||
reports_response(@merge_request.find_coverage_reports)
|
reports_response(@merge_request.find_coverage_reports)
|
||||||
|
|
|
@ -49,6 +49,15 @@ module Types
|
||||||
field :mode, type: GraphQL::STRING_TYPE,
|
field :mode, type: GraphQL::STRING_TYPE,
|
||||||
description: 'Blob mode',
|
description: 'Blob mode',
|
||||||
null: true
|
null: true
|
||||||
|
|
||||||
|
field :external_storage, type: GraphQL::STRING_TYPE,
|
||||||
|
description: 'Blob external storage',
|
||||||
|
null: true
|
||||||
|
|
||||||
|
field :rendered_as_text, type: GraphQL::BOOLEAN_TYPE,
|
||||||
|
description: 'Shows whether the blob is rendered as text',
|
||||||
|
method: :rendered_as_text?,
|
||||||
|
null: false
|
||||||
end
|
end
|
||||||
# rubocop: enable Graphql/AuthorizeTypes
|
# rubocop: enable Graphql/AuthorizeTypes
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,12 +4,14 @@ module EventsHelper
|
||||||
ICON_NAMES_BY_EVENT_TYPE = {
|
ICON_NAMES_BY_EVENT_TYPE = {
|
||||||
'pushed to' => 'commit',
|
'pushed to' => 'commit',
|
||||||
'pushed new' => 'commit',
|
'pushed new' => 'commit',
|
||||||
|
'updated' => 'commit',
|
||||||
'created' => 'status_open',
|
'created' => 'status_open',
|
||||||
'opened' => 'status_open',
|
'opened' => 'status_open',
|
||||||
'closed' => 'status_closed',
|
'closed' => 'status_closed',
|
||||||
'accepted' => 'fork',
|
'accepted' => 'fork',
|
||||||
'commented on' => 'comment',
|
'commented on' => 'comment',
|
||||||
'deleted' => 'remove',
|
'deleted' => 'remove',
|
||||||
|
'destroyed' => 'remove',
|
||||||
'imported' => 'import',
|
'imported' => 'import',
|
||||||
'joined' => 'users'
|
'joined' => 'users'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
|
@ -525,12 +525,14 @@ class Project < ApplicationRecord
|
||||||
def self.public_or_visible_to_user(user = nil, min_access_level = nil)
|
def self.public_or_visible_to_user(user = nil, min_access_level = nil)
|
||||||
min_access_level = nil if user&.admin?
|
min_access_level = nil if user&.admin?
|
||||||
|
|
||||||
if user
|
return public_to_user unless user
|
||||||
|
|
||||||
|
if user.is_a?(DeployToken)
|
||||||
|
user.projects
|
||||||
|
else
|
||||||
where('EXISTS (?) OR projects.visibility_level IN (?)',
|
where('EXISTS (?) OR projects.visibility_level IN (?)',
|
||||||
user.authorizations_for_projects(min_access_level: min_access_level),
|
user.authorizations_for_projects(min_access_level: min_access_level),
|
||||||
Gitlab::VisibilityLevel.levels_for_user(user))
|
Gitlab::VisibilityLevel.levels_for_user(user))
|
||||||
else
|
|
||||||
public_to_user
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -249,15 +249,12 @@ class User < ApplicationRecord
|
||||||
enum layout: { fixed: 0, fluid: 1 }
|
enum layout: { fixed: 0, fluid: 1 }
|
||||||
|
|
||||||
# User's Dashboard preference
|
# User's Dashboard preference
|
||||||
# Note: When adding an option, it MUST go on the end of the array.
|
|
||||||
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 }
|
||||||
|
|
||||||
# User's Project preference
|
# User's Project preference
|
||||||
# Note: When adding an option, it MUST go on the end of the array.
|
|
||||||
enum project_view: { readme: 0, activity: 1, files: 2 }
|
enum project_view: { readme: 0, activity: 1, files: 2 }
|
||||||
|
|
||||||
# User's role
|
# User's role
|
||||||
# Note: When adding an option, it MUST go on the end of the array.
|
|
||||||
enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true
|
enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true
|
||||||
|
|
||||||
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Include this module if we want to pass something else than the user to
|
# Include this module to have an object respond to user messages without being
|
||||||
# check policies. This defines several methods which the policy checker
|
# a user.
|
||||||
# would call and check.
|
#
|
||||||
|
# Use Case 1:
|
||||||
|
# Pass something else than the user to check policies. This defines several
|
||||||
|
# methods which the policy checker would call and check.
|
||||||
|
#
|
||||||
|
# Use Case 2:
|
||||||
|
# Access the API with non-user object such as deploy tokens. This defines
|
||||||
|
# several methods which the API auth flow would call.
|
||||||
module PolicyActor
|
module PolicyActor
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
@ -37,6 +44,30 @@ module PolicyActor
|
||||||
def alert_bot?
|
def alert_bot?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deactivated?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirmation_required_on_sign_in?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def can?(action, subject = :global)
|
||||||
|
Ability.allowed?(self, action, subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
def preferred_language
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def requires_ldap_check?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_obtain_ldap_lease
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PolicyActor.prepend_if_ee('EE::PolicyActor')
|
PolicyActor.prepend_if_ee('EE::PolicyActor')
|
||||||
|
|
|
@ -84,6 +84,16 @@ class ProjectPolicy < BasePolicy
|
||||||
project.merge_requests_allowing_push_to_user(user).any?
|
project.merge_requests_allowing_push_to_user(user).any?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "Deploy token with read_package_registry scope"
|
||||||
|
condition(:read_package_registry_deploy_token) do
|
||||||
|
user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Deploy token with write_package_registry scope"
|
||||||
|
condition(:write_package_registry_deploy_token) do
|
||||||
|
user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_package_registry
|
||||||
|
end
|
||||||
|
|
||||||
with_scope :subject
|
with_scope :subject
|
||||||
condition(:forking_allowed) do
|
condition(:forking_allowed) do
|
||||||
@subject.feature_available?(:forking, @user)
|
@subject.feature_available?(:forking, @user)
|
||||||
|
@ -532,6 +542,16 @@ class ProjectPolicy < BasePolicy
|
||||||
prevent :destroy_design
|
prevent :destroy_design
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rule { read_package_registry_deploy_token }.policy do
|
||||||
|
enable :read_package
|
||||||
|
enable :read_project
|
||||||
|
end
|
||||||
|
|
||||||
|
rule { write_package_registry_deploy_token }.policy do
|
||||||
|
enable :create_package
|
||||||
|
enable :read_project
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def team_member?
|
def team_member?
|
||||||
|
|
|
@ -103,17 +103,19 @@ module Auth
|
||||||
|
|
||||||
return unless requested_project
|
return unless requested_project
|
||||||
|
|
||||||
actions = actions.select do |action|
|
authorized_actions = actions.select do |action|
|
||||||
can_access?(requested_project, action)
|
can_access?(requested_project, action)
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless actions.present?
|
log_if_actions_denied(type, requested_project, actions, authorized_actions)
|
||||||
|
|
||||||
|
return unless authorized_actions.present?
|
||||||
|
|
||||||
# At this point user/build is already authenticated.
|
# At this point user/build is already authenticated.
|
||||||
#
|
#
|
||||||
ensure_container_repository!(path, actions)
|
ensure_container_repository!(path, authorized_actions)
|
||||||
|
|
||||||
{ type: type, name: path.to_s, actions: actions }
|
{ type: type, name: path.to_s, actions: authorized_actions }
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -222,5 +224,22 @@ module Auth
|
||||||
REGISTRY_LOGIN_ABILITIES.include?(ability)
|
REGISTRY_LOGIN_ABILITIES.include?(ability)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions)
|
||||||
|
return if requested_actions == authorized_actions
|
||||||
|
|
||||||
|
log_info = {
|
||||||
|
message: "Denied container registry permissions",
|
||||||
|
scope_type: type,
|
||||||
|
requested_project_path: requested_project.full_path,
|
||||||
|
requested_actions: requested_actions,
|
||||||
|
authorized_actions: authorized_actions,
|
||||||
|
username: current_user&.username,
|
||||||
|
user_id: current_user&.id,
|
||||||
|
project_path: project&.full_path
|
||||||
|
}.compact
|
||||||
|
|
||||||
|
Gitlab::AuthLogger.warn(log_info)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
- if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true)
|
- if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true)
|
||||||
- is_group = !@group.nil?
|
- is_group = !@group.nil?
|
||||||
|
|
||||||
#js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex} }
|
#js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} }
|
||||||
|
|
||||||
- else
|
- else
|
||||||
.row
|
.row
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
= content_for :meta_tags do
|
= content_for :meta_tags do
|
||||||
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
|
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
|
||||||
|
|
||||||
|
- if @project.jira_issues_import_feature_flag_enabled?
|
||||||
|
.js-projects-issues-root{ data: { can_edit: can?(current_user, :admin_project, @project).to_s,
|
||||||
|
is_jira_configured: @project.jira_service.present?.to_s,
|
||||||
|
project_path: @project.full_path } }
|
||||||
|
|
||||||
- if project_issues(@project).exists?
|
- if project_issues(@project).exists?
|
||||||
.top-area
|
.top-area
|
||||||
= render 'shared/issuable/nav', type: :issues
|
= render 'shared/issuable/nav', type: :issues
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Deploy token authentication for API with Maven endpoints
|
||||||
|
merge_request: 30332
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add alert on project issues page to show Jira import is in progress
|
||||||
|
merge_request: 31329
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove ruby_memory_bytes metric, duplicate of ruby_process_resident_memory_bytes
|
||||||
|
merge_request: 31705
|
||||||
|
author:
|
||||||
|
type: removed
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: Fixes bug where variables were not protected by default when using the correct
|
||||||
|
CI/CD admin setting
|
||||||
|
merge_request: 31655
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add Activity icons for Wiki updated and destroyed events
|
||||||
|
merge_request: 30349
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add fields to GraphQL snippet blob type
|
||||||
|
merge_request: 31710
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Log when container registry permissions are denied
|
||||||
|
merge_request: 31536
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -14,6 +14,7 @@ resources :merge_requests, concerns: :awardable, except: [:new, :create, :show],
|
||||||
post :rebase
|
post :rebase
|
||||||
get :test_reports
|
get :test_reports
|
||||||
get :exposed_artifacts
|
get :exposed_artifacts
|
||||||
|
get :accessibility_reports
|
||||||
get :coverage_reports
|
get :coverage_reports
|
||||||
get :terraform_reports
|
get :terraform_reports
|
||||||
|
|
||||||
|
|
|
@ -183,7 +183,6 @@ Some basic Ruby runtime metrics are available:
|
||||||
| `ruby_gc_duration_seconds` | Counter | 11.1 | Time spent by Ruby in GC |
|
| `ruby_gc_duration_seconds` | Counter | 11.1 | Time spent by Ruby in GC |
|
||||||
| `ruby_gc_stat_...` | Gauge | 11.1 | Various metrics from [GC.stat](https://ruby-doc.org/core-2.6.5/GC.html#method-c-stat) |
|
| `ruby_gc_stat_...` | Gauge | 11.1 | Various metrics from [GC.stat](https://ruby-doc.org/core-2.6.5/GC.html#method-c-stat) |
|
||||||
| `ruby_file_descriptors` | Gauge | 11.1 | File descriptors per process |
|
| `ruby_file_descriptors` | Gauge | 11.1 | File descriptors per process |
|
||||||
| `ruby_memory_bytes` | Gauge | 11.1 | Memory usage by process |
|
|
||||||
| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
|
| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
|
||||||
| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |
|
| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |
|
||||||
| `ruby_process_max_fds` | Gauge | 12.0 | Maximum number of open file descriptors per process |
|
| `ruby_process_max_fds` | Gauge | 12.0 | Maximum number of open file descriptors per process |
|
||||||
|
|
|
@ -9399,6 +9399,11 @@ type SnippetBlob {
|
||||||
"""
|
"""
|
||||||
binary: Boolean!
|
binary: Boolean!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Blob external storage
|
||||||
|
"""
|
||||||
|
externalStorage: String
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Blob mode
|
Blob mode
|
||||||
"""
|
"""
|
||||||
|
@ -9424,6 +9429,11 @@ type SnippetBlob {
|
||||||
"""
|
"""
|
||||||
rawPath: String!
|
rawPath: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Shows whether the blob is rendered as text
|
||||||
|
"""
|
||||||
|
renderedAsText: Boolean!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Blob highlighted data
|
Blob highlighted data
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -27882,6 +27882,20 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "externalStorage",
|
||||||
|
"description": "Blob external storage",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "mode",
|
"name": "mode",
|
||||||
"description": "Blob mode",
|
"description": "Blob mode",
|
||||||
|
@ -27956,6 +27970,24 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "renderedAsText",
|
||||||
|
"description": "Shows whether the blob is rendered as text",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "richData",
|
"name": "richData",
|
||||||
"description": "Blob highlighted data",
|
"description": "Blob highlighted data",
|
||||||
|
|
|
@ -1392,11 +1392,13 @@ Represents the snippet blob
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| --- | ---- | ---------- |
|
| --- | ---- | ---------- |
|
||||||
| `binary` | Boolean! | Shows whether the blob is binary |
|
| `binary` | Boolean! | Shows whether the blob is binary |
|
||||||
|
| `externalStorage` | String | Blob external storage |
|
||||||
| `mode` | String | Blob mode |
|
| `mode` | String | Blob mode |
|
||||||
| `name` | String | Blob name |
|
| `name` | String | Blob name |
|
||||||
| `path` | String | Blob path |
|
| `path` | String | Blob path |
|
||||||
| `plainData` | String | Blob plain highlighted data |
|
| `plainData` | String | Blob plain highlighted data |
|
||||||
| `rawPath` | String! | Blob raw content endpoint path |
|
| `rawPath` | String! | Blob raw content endpoint path |
|
||||||
|
| `renderedAsText` | Boolean! | Shows whether the blob is rendered as text |
|
||||||
| `richData` | String | Blob highlighted data |
|
| `richData` | String | Blob highlighted data |
|
||||||
| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
|
| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
|
||||||
| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |
|
| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |
|
||||||
|
|
|
@ -1067,7 +1067,9 @@ The only clauses currently available are:
|
||||||
|
|
||||||
Keywords such as `branches` or `refs` that are currently available for
|
Keywords such as `branches` or `refs` that are currently available for
|
||||||
`only`/`except` are not yet available in `rules` as they are being individually
|
`only`/`except` are not yet available in `rules` as they are being individually
|
||||||
considered for their usage and behavior in this context.
|
considered for their usage and behavior in this context. Future keyword improvements
|
||||||
|
are being discussed in our [epic for improving `rules`](https://gitlab.com/groups/gitlab-org/-/epics/2783),
|
||||||
|
where anyone can add suggestions or requests.
|
||||||
|
|
||||||
#### Permitted attributes
|
#### Permitted attributes
|
||||||
|
|
||||||
|
|
|
@ -3,35 +3,40 @@
|
||||||
Thank you for your interest in contributing to GitLab. This guide details how
|
Thank you for your interest in contributing to GitLab. This guide details how
|
||||||
to contribute to GitLab in a way that is easy for everyone.
|
to contribute to GitLab in a way that is easy for everyone.
|
||||||
|
|
||||||
For a first-time step-by-step guide to the contribution process, please see
|
For a first-time step-by-step guide to the contribution process, see our
|
||||||
["Contributing to GitLab"](https://about.gitlab.com/community/contribute/).
|
[Contributing to GitLab](https://about.gitlab.com/community/contribute/) page.
|
||||||
|
|
||||||
Looking for something to work on? Look for issues with the label [`Accepting merge requests`](#i-want-to-contribute).
|
Looking for something to work on? Look for issues with the label
|
||||||
|
[`~Accepting merge requests`](#how-to-contribute).
|
||||||
|
|
||||||
GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
|
GitLab comes in two flavors:
|
||||||
source edition, and GitLab Enterprise Edition (EE) which is our commercial
|
|
||||||
edition. Throughout this guide you will see references to CE and EE for
|
|
||||||
abbreviation.
|
|
||||||
|
|
||||||
To get an overview of GitLab community membership including those that would be reviewing or merging your contributions, please visit [the community roles page](community_roles.md).
|
- GitLab Community Edition (CE), our free and open source edition.
|
||||||
|
- GitLab Enterprise Edition (EE), which is our commercial edition.
|
||||||
|
|
||||||
|
Throughout this guide you will see references to CE and EE for abbreviation.
|
||||||
|
|
||||||
|
To get an overview of GitLab community membership, including those that would review or merge
|
||||||
|
your contributions, visit [the community roles page](community_roles.md).
|
||||||
|
|
||||||
If you want to know how the GitLab [core team](https://about.gitlab.com/community/core-team/)
|
If you want to know how the GitLab [core team](https://about.gitlab.com/community/core-team/)
|
||||||
operates please see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab/blob/master/PROCESS.md).
|
operates, see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab/blob/master/PROCESS.md).
|
||||||
|
|
||||||
[GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
|
GitLab Inc engineers should refer to the [engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/).
|
||||||
|
|
||||||
## Security vulnerability disclosure
|
## Security vulnerability disclosure
|
||||||
|
|
||||||
Please report suspected security vulnerabilities in private to
|
Report suspected security vulnerabilities in private to
|
||||||
`support@gitlab.com`, also see the
|
`support@gitlab.com`, also see the
|
||||||
[disclosure section on the GitLab.com website](https://about.gitlab.com/security/disclosure/).
|
[disclosure section on the GitLab.com website](https://about.gitlab.com/security/disclosure/).
|
||||||
Please do **NOT** create publicly viewable issues for suspected security
|
|
||||||
vulnerabilities.
|
DANGER: **Danger:**
|
||||||
|
Do **NOT** create publicly viewable issues for suspected security vulnerabilities.
|
||||||
|
|
||||||
## Code of conduct
|
## Code of conduct
|
||||||
|
|
||||||
We want to create a welcoming environment for everyone who is interested in contributing.
|
We want to create a welcoming environment for everyone who is interested in contributing.
|
||||||
Please visit our [Code of Conduct page](https://about.gitlab.com/community/contribute/code-of-conduct/) to learn more about our commitment to an open and welcoming environment.
|
Visit our [Code of Conduct page](https://about.gitlab.com/community/contribute/code-of-conduct/) to learn more about our commitment to an open and welcoming environment.
|
||||||
|
|
||||||
## Closing policy for issues and merge requests
|
## Closing policy for issues and merge requests
|
||||||
|
|
||||||
|
@ -40,39 +45,56 @@ and merge requests is limited. Out of respect for our volunteers, issues and
|
||||||
merge requests not in line with the guidelines listed in this document may be
|
merge requests not in line with the guidelines listed in this document may be
|
||||||
closed without notice.
|
closed without notice.
|
||||||
|
|
||||||
Please treat our volunteers with courtesy and respect, it will go a long way
|
Treat our volunteers with courtesy and respect, it will go a long way
|
||||||
towards getting your issue resolved.
|
towards getting your issue resolved.
|
||||||
|
|
||||||
Issues and merge requests should be in English and contain appropriate language
|
Issues and merge requests should be in English and contain appropriate language
|
||||||
for audiences of all ages.
|
for audiences of all ages.
|
||||||
|
|
||||||
If a contributor is no longer actively working on a submitted merge request
|
If a contributor is no longer actively working on a submitted merge request,
|
||||||
we can decide that the merge request will be finished by one of our
|
we can:
|
||||||
[Merge request coaches](https://about.gitlab.com/company/team/) or close the merge request. We make this decision
|
|
||||||
based on how important the change is for our product vision. If a merge request
|
- Decide that the merge request will be finished by one of our
|
||||||
coach is going to finish the merge request we assign the
|
[Merge request coaches](https://about.gitlab.com/company/team/).
|
||||||
~"coach will finish" label. When a team member picks up a community contribution,
|
- Close the merge request.
|
||||||
|
|
||||||
|
We make this decision based on how important the change is for our product vision. If a merge
|
||||||
|
request coach is going to finish the merge request, we assign the
|
||||||
|
`~coach will finish` label.
|
||||||
|
|
||||||
|
When a team member picks up a community contribution,
|
||||||
we credit the original author by adding a changelog entry crediting the author
|
we credit the original author by adding a changelog entry crediting the author
|
||||||
and optionally include the original author on at least one of the commits
|
and optionally include the original author on at least one of the commits
|
||||||
within the MR.
|
within the MR.
|
||||||
|
|
||||||
## Helping others
|
## Helping others
|
||||||
|
|
||||||
Please help other GitLab users when you can.
|
Help other GitLab users when you can.
|
||||||
The methods people will use to seek help can be found on the [getting help page](https://about.gitlab.com/get-help/).
|
The methods people use to seek help can be found on the [getting help page](https://about.gitlab.com/get-help/).
|
||||||
|
|
||||||
Sign up for the mailing list, answer GitLab questions on StackOverflow or
|
Sign up for the mailing list, answer GitLab questions on StackOverflow or respond in the IRC channel.
|
||||||
respond in the IRC channel.
|
|
||||||
|
|
||||||
## I want to contribute
|
## How to contribute
|
||||||
|
|
||||||
If you want to contribute to GitLab,
|
If you want to contribute to GitLab,
|
||||||
[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
|
[issues with the `~Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
|
||||||
are a great place to start.
|
are a great place to start.
|
||||||
|
|
||||||
If you have any questions or need help visit [Getting Help](https://about.gitlab.com/get-help/) to
|
If you have any questions or need help visit [Getting Help](https://about.gitlab.com/get-help/) to
|
||||||
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
|
learn how to communicate with GitLab. We have a [Gitter channel for contributors](https://gitter.im/gitlab/contributors),
|
||||||
please consider we favor
|
however we favor
|
||||||
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
|
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication.
|
||||||
|
|
||||||
|
Thanks for your contribution!
|
||||||
|
|
||||||
|
### GitLab Development Kit
|
||||||
|
|
||||||
|
The GitLab Development Kit (GDK) helps contributors run a local GitLab instance with all the
|
||||||
|
required dependencies. It can be used to test changes to GitLab and related projects before raising
|
||||||
|
a Merge Request.
|
||||||
|
|
||||||
|
For more information, see the [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit)
|
||||||
|
project.
|
||||||
|
|
||||||
## Contribution Flow
|
## Contribution Flow
|
||||||
|
|
||||||
|
@ -92,7 +114,7 @@ When submitting code to GitLab, you may feel that your contribution requires the
|
||||||
|
|
||||||
When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
|
When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
|
||||||
|
|
||||||
## Issues workflow
|
### Issues workflow
|
||||||
|
|
||||||
This [documentation](issue_workflow.md) outlines the current issue workflow:
|
This [documentation](issue_workflow.md) outlines the current issue workflow:
|
||||||
|
|
||||||
|
@ -105,7 +127,7 @@ This [documentation](issue_workflow.md) outlines the current issue workflow:
|
||||||
- [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
|
- [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
|
||||||
- [Technical debt in follow-up issues](issue_workflow.md#technical-debt-in-follow-up-issues)
|
- [Technical debt in follow-up issues](issue_workflow.md#technical-debt-in-follow-up-issues)
|
||||||
|
|
||||||
## Merge requests workflow
|
### Merge requests workflow
|
||||||
|
|
||||||
This [documentation](merge_request_workflow.md) outlines the current merge request process.
|
This [documentation](merge_request_workflow.md) outlines the current merge request process.
|
||||||
|
|
||||||
|
@ -120,13 +142,15 @@ This [documentation](style_guides.md) outlines the current style guidelines.
|
||||||
|
|
||||||
## Implement design & UI elements
|
## Implement design & UI elements
|
||||||
|
|
||||||
This [design documentation](design.md) outlines the current process for implementing
|
This [design documentation](design.md) outlines the current process for implementing design and UI
|
||||||
design & UI elements.
|
elements.
|
||||||
|
|
||||||
|
## Contribute documentation
|
||||||
|
|
||||||
|
For information on how to contribute documentation, see GitLab
|
||||||
|
[documentation guidelines](../documentation/index.md).
|
||||||
|
|
||||||
## Getting an Enterprise Edition License
|
## Getting an Enterprise Edition License
|
||||||
|
|
||||||
If you need a license for contributing to an EE-feature, please [follow these instructions](https://about.gitlab.com/handbook/marketing/community-relations/code-contributor-program/#for-contributors-to-the-gitlab-enterprise-edition-ee).
|
If you need a license for contributing to an EE-feature, see
|
||||||
|
[relevant information](https://about.gitlab.com/handbook/marketing/community-relations/code-contributor-program/#for-contributors-to-the-gitlab-enterprise-edition-ee).
|
||||||
---
|
|
||||||
|
|
||||||
[Return to Development documentation](../README.md)
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ Merge requests should be submitted to the appropriate project at GitLab.com, for
|
||||||
[Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests), etc.
|
[Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests), etc.
|
||||||
|
|
||||||
If you are new to GitLab development (or web development in general), see the
|
If you are new to GitLab development (or web development in general), see the
|
||||||
[I want to contribute!](index.md#i-want-to-contribute) section to get started with
|
[how to contribute](index.md#how-to-contribute) section to get started with
|
||||||
some potentially easy issues.
|
some potentially easy issues.
|
||||||
|
|
||||||
To start developing GitLab, download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit)
|
To start developing GitLab, download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit)
|
||||||
|
|
|
@ -16,7 +16,7 @@ Component's computed properties / methods or external helpers.
|
||||||
|
|
||||||
**Why?**
|
**Why?**
|
||||||
|
|
||||||
`$on` and `$off` methods [are removed](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md) from the Vue instance, so in Vue 3 it can't be used to create an event hub.
|
`$on`, `$once`, and `$off` methods [are removed](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md) from the Vue instance, so in Vue 3 it can't be used to create an event hub.
|
||||||
|
|
||||||
**What to use instead**
|
**What to use instead**
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ import createEventHub from '~/helpers/event_hub_factory';
|
||||||
export default createEventHub();
|
export default createEventHub();
|
||||||
```
|
```
|
||||||
|
|
||||||
Event hubs created with the factory expose the same methods as Vue 2 event hubs (`$on`, `$off` and
|
Event hubs created with the factory expose the same methods as Vue 2 event hubs (`$on`, `$once`, `$off` and
|
||||||
`$emit`), making them backward compatible with our previous approach.
|
`$emit`), making them backward compatible with our previous approach.
|
||||||
|
|
||||||
## <template functional>
|
## <template functional>
|
||||||
|
|
|
@ -318,19 +318,41 @@ stub_feature_flags(ci_live_trace: false)
|
||||||
Feature.enabled?(:ci_live_trace) # => false
|
Feature.enabled?(:ci_live_trace) # => false
|
||||||
```
|
```
|
||||||
|
|
||||||
If you wish to set up a test where a feature flag is disabled for some
|
If you wish to set up a test where a feature flag is enabled only
|
||||||
actors and not others, you can specify this in options passed to the
|
for some actors and not others, you can specify this in options
|
||||||
helper. For example, to disable the `ci_live_trace` feature flag for a
|
passed to the helper. For example, to enable the `ci_live_trace`
|
||||||
specifc project:
|
feature flag for a specifc project:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
project1, project2 = build_list(:project, 2)
|
project1, project2 = build_list(:project, 2)
|
||||||
|
|
||||||
# Feature will only be disabled for project1
|
# Feature will only be enabled for project1
|
||||||
stub_feature_flags(ci_live_trace: { enabled: false, thing: project1 })
|
stub_feature_flags(ci_live_trace: project1)
|
||||||
|
|
||||||
Feature.enabled?(:ci_live_trace, project1) # => false
|
Feature.enabled?(:ci_live_trace) # => false
|
||||||
Feature.enabled?(:ci_live_trace, project2) # => true
|
Feature.enabled?(:ci_live_trace, project1) # => true
|
||||||
|
Feature.enabled?(:ci_live_trace, project2) # => false
|
||||||
|
```
|
||||||
|
|
||||||
|
This represents an actual behavior of FlipperGate:
|
||||||
|
|
||||||
|
1. You can enable an override for a specified actor to be enabled
|
||||||
|
1. You can disable (remove) an override for a specified actor,
|
||||||
|
fallbacking to default state
|
||||||
|
1. There's no way to model that you explicitly disable a specified actor
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Feature.enable(:my_feature)
|
||||||
|
Feature.disable(:my_feature, project1)
|
||||||
|
Feature.enabled?(:my_feature) # => true
|
||||||
|
Feature.enabled?(:my_feature, project1) # => true
|
||||||
|
```
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Feature.disable(:my_feature2)
|
||||||
|
Feature.enable(:my_feature2, project1)
|
||||||
|
Feature.enabled?(:my_feature2) # => false
|
||||||
|
Feature.enabled?(:my_feature2, project1) # => true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pristine test environments
|
### Pristine test environments
|
||||||
|
|
|
@ -404,6 +404,9 @@ As of GitLab 10.0, the supported buildpacks are:
|
||||||
- buildpack-nginx v8
|
- buildpack-nginx v8
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If your application needs a buildpack that is not in the above list, you
|
||||||
|
might want to use a [custom buildpack](customize.md#custom-buildpacks).
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
The following restrictions apply.
|
The following restrictions apply.
|
||||||
|
|
|
@ -207,9 +207,9 @@ Enter a project name or hit enter to use the directory name as project name.
|
||||||
The next step is to add the GitLab Package Registry as a Maven remote. If a
|
The next step is to add the GitLab Package Registry as a Maven remote. If a
|
||||||
project is private or you want to upload Maven artifacts to GitLab,
|
project is private or you want to upload Maven artifacts to GitLab,
|
||||||
credentials will need to be provided for authorization too. Support is available
|
credentials will need to be provided for authorization too. Support is available
|
||||||
for [personal access tokens](#authenticating-with-a-personal-access-token) and
|
for [personal access tokens](#authenticating-with-a-personal-access-token),
|
||||||
[CI job tokens](#authenticating-with-a-ci-job-token) only.
|
[CI job tokens](#authenticating-with-a-ci-job-token), and
|
||||||
[Deploy tokens](../../project/deploy_tokens/index.md) and regular username/password
|
[deploy tokens](../../project/deploy_tokens/index.md) only. Regular username/password
|
||||||
credentials do not work.
|
credentials do not work.
|
||||||
|
|
||||||
### Authenticating with a personal access token
|
### Authenticating with a personal access token
|
||||||
|
@ -324,6 +324,59 @@ repositories {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Authenticating with a deploy token
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0.
|
||||||
|
|
||||||
|
To authenticate with a [deploy token](./../../project/deploy_tokens/index.md),
|
||||||
|
set the scope to `api` when creating one, and add it to your Maven or Gradle configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
#### Authenticating with a deploy token in Maven
|
||||||
|
|
||||||
|
Add a corresponding section to your
|
||||||
|
[`settings.xml`](https://maven.apache.org/settings.html) file:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<settings>
|
||||||
|
<servers>
|
||||||
|
<server>
|
||||||
|
<id>gitlab-maven</id>
|
||||||
|
<configuration>
|
||||||
|
<httpHeaders>
|
||||||
|
<property>
|
||||||
|
<name>Deploy-Token</name>
|
||||||
|
<value>REPLACE_WITH_YOUR_DEPLOY_TOKEN</value>
|
||||||
|
</property>
|
||||||
|
</httpHeaders>
|
||||||
|
</configuration>
|
||||||
|
</server>
|
||||||
|
</servers>
|
||||||
|
</settings>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Authenticating with a deploy token in Gradle
|
||||||
|
|
||||||
|
To authenticate with a deploy token, add a repositories section to your
|
||||||
|
[`build.gradle`](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html)
|
||||||
|
file:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url "https://<gitlab-url>/api/v4/groups/<group>/-/packages/maven"
|
||||||
|
name "GitLab"
|
||||||
|
credentials(HttpHeaderCredentials) {
|
||||||
|
name = 'Deploy-Token'
|
||||||
|
value = '<deploy-token>'
|
||||||
|
}
|
||||||
|
authentication {
|
||||||
|
header(HttpHeaderAuthentication)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Configuring your project to use the GitLab Maven repository URL
|
## Configuring your project to use the GitLab Maven repository URL
|
||||||
|
|
||||||
To download and upload packages from GitLab, you need a `repository` and
|
To download and upload packages from GitLab, you need a `repository` and
|
||||||
|
@ -397,7 +450,7 @@ project's ID can be used for uploading.
|
||||||
|
|
||||||
### Group level Maven endpoint
|
### Group level Maven endpoint
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8798) in GitLab Premium 11.7.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8798) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
|
||||||
|
|
||||||
If you rely on many packages, it might be inefficient to include the `repository` section
|
If you rely on many packages, it might be inefficient to include the `repository` section
|
||||||
with a unique URL for each package. Instead, you can use the group level endpoint for
|
with a unique URL for each package. Instead, you can use the group level endpoint for
|
||||||
|
@ -460,7 +513,7 @@ For retrieving artifacts, you can use either the
|
||||||
|
|
||||||
### Instance level Maven endpoint
|
### Instance level Maven endpoint
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8274) in GitLab Premium 11.7.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8274) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
|
||||||
|
|
||||||
If you rely on many packages, it might be inefficient to include the `repository` section
|
If you rely on many packages, it might be inefficient to include the `repository` section
|
||||||
with a unique URL for each package. Instead, you can use the instance level endpoint for
|
with a unique URL for each package. Instead, you can use the instance level endpoint for
|
||||||
|
|
|
@ -65,6 +65,7 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_user_from_sources
|
def find_user_from_sources
|
||||||
|
deploy_token_from_request ||
|
||||||
find_user_from_access_token ||
|
find_user_from_access_token ||
|
||||||
find_user_from_job_token ||
|
find_user_from_job_token ||
|
||||||
find_user_from_warden
|
find_user_from_warden
|
||||||
|
@ -90,12 +91,16 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_access_allowed?(user)
|
def api_access_allowed?(user)
|
||||||
Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
|
user_allowed_or_deploy_token?(user) && user.can?(:access_api)
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_access_denied_message(user)
|
def api_access_denied_message(user)
|
||||||
Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
|
Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_allowed_or_deploy_token?(user)
|
||||||
|
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
|
|
|
@ -25,6 +25,7 @@ module Gitlab
|
||||||
PRIVATE_TOKEN_PARAM = :private_token
|
PRIVATE_TOKEN_PARAM = :private_token
|
||||||
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
|
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
|
||||||
JOB_TOKEN_PARAM = :job_token
|
JOB_TOKEN_PARAM = :job_token
|
||||||
|
DEPLOY_TOKEN_HEADER = 'HTTP_DEPLOY_TOKEN'.freeze
|
||||||
RUNNER_TOKEN_PARAM = :token
|
RUNNER_TOKEN_PARAM = :token
|
||||||
RUNNER_JOB_TOKEN_PARAM = :token
|
RUNNER_JOB_TOKEN_PARAM = :token
|
||||||
|
|
||||||
|
@ -101,6 +102,16 @@ module Gitlab
|
||||||
access_token.user || raise(UnauthorizedError)
|
access_token.user || raise(UnauthorizedError)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This returns a deploy token, not a user since a deploy token does not
|
||||||
|
# belong to a user.
|
||||||
|
def deploy_token_from_request
|
||||||
|
return unless route_authentication_setting[:deploy_token_allowed]
|
||||||
|
|
||||||
|
token = current_request.env[DEPLOY_TOKEN_HEADER].presence
|
||||||
|
|
||||||
|
DeployToken.active.find_by_token(token)
|
||||||
|
end
|
||||||
|
|
||||||
def find_runner_from_token
|
def find_runner_from_token
|
||||||
return unless api_request?
|
return unless api_request?
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def sample_memory_usage
|
def sample_memory_usage
|
||||||
add_metric('memory_usage', value: System.memory_usage)
|
add_metric('memory_usage', value: System.memory_usage_rss)
|
||||||
end
|
end
|
||||||
|
|
||||||
def sample_file_descriptors
|
def sample_file_descriptors
|
||||||
|
|
|
@ -35,7 +35,6 @@ module Gitlab
|
||||||
def init_metrics
|
def init_metrics
|
||||||
metrics = {
|
metrics = {
|
||||||
file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels),
|
file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels),
|
||||||
memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used (RSS)', labels),
|
|
||||||
process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
|
process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
|
||||||
process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
|
process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
|
||||||
process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used (RSS)', labels),
|
process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used (RSS)', labels),
|
||||||
|
@ -87,9 +86,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_memory_usage_metrics
|
def set_memory_usage_metrics
|
||||||
memory_rss = System.memory_usage
|
metrics[:process_resident_memory_bytes].set(labels, System.memory_usage_rss)
|
||||||
metrics[:memory_bytes].set(labels, memory_rss)
|
|
||||||
metrics[:process_resident_memory_bytes].set(labels, memory_rss)
|
|
||||||
|
|
||||||
if Gitlab::Utils.to_boolean(ENV['enable_memory_uss_pss'])
|
if Gitlab::Utils.to_boolean(ENV['enable_memory_uss_pss'])
|
||||||
memory_uss_pss = System.memory_usage_uss_pss
|
memory_uss_pss = System.memory_usage_uss_pss
|
||||||
|
|
|
@ -18,7 +18,7 @@ module Gitlab
|
||||||
MAX_OPEN_FILES_PATTERN = /Max open files\s*(?<value>\d+)/.freeze
|
MAX_OPEN_FILES_PATTERN = /Max open files\s*(?<value>\d+)/.freeze
|
||||||
|
|
||||||
# Returns the current process' RSS (resident set size) in bytes.
|
# Returns the current process' RSS (resident set size) in bytes.
|
||||||
def self.memory_usage
|
def self.memory_usage_rss
|
||||||
sum_matches(PROC_STATUS_PATH, rss: RSS_PATTERN)[:rss].kilobytes
|
sum_matches(PROC_STATUS_PATH, rss: RSS_PATTERN)[:rss].kilobytes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -55,13 +55,13 @@ module Gitlab
|
||||||
def run
|
def run
|
||||||
Thread.current[THREAD_KEY] = self
|
Thread.current[THREAD_KEY] = self
|
||||||
|
|
||||||
@memory_before = System.memory_usage
|
@memory_before = System.memory_usage_rss
|
||||||
@started_at = System.monotonic_time
|
@started_at = System.monotonic_time
|
||||||
@thread_cputime_start = System.thread_cpu_time
|
@thread_cputime_start = System.thread_cpu_time
|
||||||
|
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
@memory_after = System.memory_usage
|
@memory_after = System.memory_usage_rss
|
||||||
@finished_at = System.monotonic_time
|
@finished_at = System.monotonic_time
|
||||||
|
|
||||||
self.class.gitlab_transaction_cputime_seconds.observe(labels, thread_cpu_duration)
|
self.class.gitlab_transaction_cputime_seconds.observe(labels, thread_cpu_duration)
|
||||||
|
|
|
@ -36,7 +36,7 @@ module Gitlab
|
||||||
gc_stats: gc_stats,
|
gc_stats: gc_stats,
|
||||||
time_to_finish: time_to_finish,
|
time_to_finish: time_to_finish,
|
||||||
number_of_sql_calls: sql_calls_count,
|
number_of_sql_calls: sql_calls_count,
|
||||||
memory_usage: "#{Gitlab::Metrics::System.memory_usage.to_f / 1024 / 1024} MiB",
|
memory_usage: "#{Gitlab::Metrics::System.memory_usage_rss.to_f / 1024 / 1024} MiB",
|
||||||
label: ::Prometheus::PidProvider.worker_id
|
label: ::Prometheus::PidProvider.worker_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1792,6 +1792,9 @@ msgstr ""
|
||||||
msgid "AlertManagement|Surface alerts in GitLab"
|
msgid "AlertManagement|Surface alerts in GitLab"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AlertManagement|There was an error displaying the alert. Please refresh the page to try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
|
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -11291,6 +11294,9 @@ msgstr ""
|
||||||
msgid "Import in progress"
|
msgid "Import in progress"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Import in progress. Refresh page to see newly added issues."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Import issues"
|
msgid "Import issues"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe Groups::Settings::IntegrationsController do
|
||||||
|
|
||||||
context 'when group_level_integrations not enabled' do
|
context 'when group_level_integrations not enabled' do
|
||||||
it 'returns not_found' do
|
it 'returns not_found' do
|
||||||
stub_feature_flags(group_level_integrations: { enabled: false, thing: group })
|
stub_feature_flags(group_level_integrations: false)
|
||||||
|
|
||||||
get :index, params: { group_id: group }
|
get :index, params: { group_id: group }
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ describe Groups::Settings::IntegrationsController do
|
||||||
|
|
||||||
context 'when group_level_integrations not enabled' do
|
context 'when group_level_integrations not enabled' do
|
||||||
it 'returns not_found' do
|
it 'returns not_found' do
|
||||||
stub_feature_flags(group_level_integrations: { enabled: false, thing: group })
|
stub_feature_flags(group_level_integrations: false)
|
||||||
|
|
||||||
get :edit, params: { group_id: group, id: Service.available_services_names.sample }
|
get :edit, params: { group_id: group, id: Service.available_services_names.sample }
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe Groups::Settings::RepositoryController do
|
||||||
describe 'POST create_deploy_token' do
|
describe 'POST create_deploy_token' do
|
||||||
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
|
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: group })
|
stub_feature_flags(ajax_new_deploy_token: false)
|
||||||
entity.add_owner(user)
|
entity.add_owner(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1341,6 +1341,141 @@ describe Projects::MergeRequestsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET accessibility_reports' do
|
||||||
|
let(:merge_request) do
|
||||||
|
create(:merge_request,
|
||||||
|
:with_diffs,
|
||||||
|
:with_merge_request_pipeline,
|
||||||
|
target_project: project,
|
||||||
|
source_project: project
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:pipeline) do
|
||||||
|
create(:ci_pipeline,
|
||||||
|
:success,
|
||||||
|
project: merge_request.source_project,
|
||||||
|
ref: merge_request.source_branch,
|
||||||
|
sha: merge_request.diff_head_sha)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(MergeRequest)
|
||||||
|
.to receive(:compare_accessibility_reports)
|
||||||
|
.and_return(accessibility_comparison)
|
||||||
|
|
||||||
|
allow_any_instance_of(MergeRequest)
|
||||||
|
.to receive(:actual_head_pipeline)
|
||||||
|
.and_return(pipeline)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
get :accessibility_reports, params: {
|
||||||
|
namespace_id: project.namespace.to_param,
|
||||||
|
project_id: project,
|
||||||
|
id: merge_request.iid
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'permissions on a public project with private CI/CD' do
|
||||||
|
let(:project) { create(:project, :repository, :public, :builds_private) }
|
||||||
|
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
|
||||||
|
|
||||||
|
context 'while signed out' do
|
||||||
|
before do
|
||||||
|
sign_out(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds with a 404' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
expect(response.body).to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'while signed in as an unrelated user' do
|
||||||
|
before do
|
||||||
|
sign_in(create(:user))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds with a 404' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
expect(response.body).to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when feature flag is disabled' do
|
||||||
|
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_feature_flags(accessibility_report_view: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 204 HTTP status' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when pipeline has jobs with accessibility reports' do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(MergeRequest)
|
||||||
|
.to receive(:has_accessibility_reports?)
|
||||||
|
.and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when processing accessibility reports is in progress' do
|
||||||
|
let(:accessibility_comparison) { { status: :parsing } }
|
||||||
|
|
||||||
|
it 'sends polling interval' do
|
||||||
|
expect(Gitlab::PollingInterval).to receive(:set_header)
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 204 HTTP status' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when processing accessibility reports is completed' do
|
||||||
|
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
|
||||||
|
|
||||||
|
it 'returns accessibility reports' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response).to eq({ 'summary' => 1 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user created corrupted accessibility reports' do
|
||||||
|
let(:accessibility_comparison) { { status: :error, status_reason: 'This merge request does not have accessibility reports' } }
|
||||||
|
|
||||||
|
it 'does not send polling interval' do
|
||||||
|
expect(Gitlab::PollingInterval).not_to receive(:set_header)
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 400 HTTP status' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
expect(json_response).to eq({ 'status_reason' => 'This merge request does not have accessibility reports' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST remove_wip' do
|
describe 'POST remove_wip' do
|
||||||
before do
|
before do
|
||||||
merge_request.title = merge_request.wip_title
|
merge_request.title = merge_request.wip_title
|
||||||
|
|
|
@ -878,7 +878,7 @@ describe Projects::PipelinesController do
|
||||||
|
|
||||||
context 'when junit_pipeline_screenshots_view is enabled' do
|
context 'when junit_pipeline_screenshots_view is enabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(junit_pipeline_screenshots_view: { enabled: true, thing: project })
|
stub_feature_flags(junit_pipeline_screenshots_view: project)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when test_report contains attachment and scope is with_attachment as a URL param' do
|
context 'when test_report contains attachment and scope is with_attachment as a URL param' do
|
||||||
|
@ -907,7 +907,7 @@ describe Projects::PipelinesController do
|
||||||
|
|
||||||
context 'when junit_pipeline_screenshots_view is disabled' do
|
context 'when junit_pipeline_screenshots_view is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(junit_pipeline_screenshots_view: { enabled: false, thing: project })
|
stub_feature_flags(junit_pipeline_screenshots_view: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when test_report contains attachment and scope is with_attachment as a URL param' do
|
context 'when test_report contains attachment and scope is with_attachment as a URL param' do
|
||||||
|
|
|
@ -36,7 +36,7 @@ describe Projects::Settings::RepositoryController do
|
||||||
describe 'POST create_deploy_token' do
|
describe 'POST create_deploy_token' do
|
||||||
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
|
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
|
stub_feature_flags(ajax_new_deploy_token: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'a created deploy token' do
|
it_behaves_like 'a created deploy token' do
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe 'Explore Groups', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_feature_flags({ vue_issuables_list: { enabled: false, thing: group } })
|
stub_feature_flags(vue_issuables_list: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'renders public and internal projects' do
|
shared_examples 'renders public and internal projects' do
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe 'Group issues page' do
|
||||||
let(:path) { issues_group_path(group) }
|
let(:path) { issues_group_path(group) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_feature_flags({ vue_issuables_list: { enabled: false, thing: group } })
|
stub_feature_flags(vue_issuables_list: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with shared examples' do
|
context 'with shared examples' do
|
||||||
|
|
|
@ -20,7 +20,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(web_ide_default: false, single_mr_diff_view: { enabled: false, thing: target_project }, code_navigation: false)
|
stub_feature_flags(web_ide_default: false, single_mr_diff_view: false, code_navigation: false)
|
||||||
|
|
||||||
target_project.add_maintainer(user)
|
target_project.add_maintainer(user)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe 'Batch diffs', :js do
|
||||||
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'empty-branch') }
|
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'empty-branch') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(single_mr_diff_view: { enabled: true, thing: project })
|
stub_feature_flags(single_mr_diff_view: project)
|
||||||
stub_feature_flags(diffs_batch_load: true)
|
stub_feature_flags(diffs_batch_load: true)
|
||||||
|
|
||||||
sign_in(project.owner)
|
sign_in(project.owner)
|
||||||
|
|
|
@ -30,7 +30,7 @@ describe 'Projects > Settings > Repository settings' do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_container_registry_config(enabled: true)
|
stub_container_registry_config(enabled: true)
|
||||||
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
|
stub_feature_flags(ajax_new_deploy_token: project)
|
||||||
visit project_settings_repository_path(project)
|
visit project_settings_repository_path(project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ describe 'Projects > Settings > Repository settings' do
|
||||||
# Removal: https://gitlab.com/gitlab-org/gitlab/-/issues/208828
|
# Removal: https://gitlab.com/gitlab-org/gitlab/-/issues/208828
|
||||||
context 'with the `keep_divergent_refs` feature flag disabled' do
|
context 'with the `keep_divergent_refs` feature flag disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(keep_divergent_refs: { enabled: false, thing: project })
|
stub_feature_flags(keep_divergent_refs: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'hides the "Keep divergent refs" option' do
|
it 'hides the "Keep divergent refs" option' do
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe 'Repository Settings > User sees revoke deploy token modal', :js do
|
||||||
before do
|
before do
|
||||||
project.add_role(user, role)
|
project.add_role(user, role)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
|
stub_feature_flags(ajax_new_deploy_token: project)
|
||||||
visit(project_settings_repository_path(project))
|
visit(project_settings_repository_path(project))
|
||||||
click_link('Revoke')
|
click_link('Revoke')
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ describe ForkProjectsFinder do
|
||||||
let(:private_fork_member) { create(:user) }
|
let(:private_fork_member) { create(:user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(object_pools: { enabled: false, thing: source_project })
|
stub_feature_flags(object_pools: source_project)
|
||||||
|
|
||||||
private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
||||||
private_fork.add_developer(private_fork_member)
|
private_fork.add_developer(private_fork_member)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { GlLoadingIcon } from '@gitlab/ui';
|
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import AlertDetails from '~/alert_management/components/alert_details.vue';
|
import AlertDetails from '~/alert_management/components/alert_details.vue';
|
||||||
|
|
||||||
describe('AlertDetails', () => {
|
describe('AlertDetails', () => {
|
||||||
|
@ -7,7 +7,7 @@ describe('AlertDetails', () => {
|
||||||
const newIssuePath = 'root/alerts/-/issues/new';
|
const newIssuePath = 'root/alerts/-/issues/new';
|
||||||
|
|
||||||
function mountComponent({
|
function mountComponent({
|
||||||
alert = {},
|
data = { alert: {} },
|
||||||
createIssueFromAlertEnabled = false,
|
createIssueFromAlertEnabled = false,
|
||||||
loading = false,
|
loading = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
@ -18,7 +18,7 @@ describe('AlertDetails', () => {
|
||||||
newIssuePath,
|
newIssuePath,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { alert };
|
return data;
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
glFeatures: { createIssueFromAlertEnabled },
|
glFeatures: { createIssueFromAlertEnabled },
|
||||||
|
@ -46,7 +46,7 @@ describe('AlertDetails', () => {
|
||||||
describe('Alert details', () => {
|
describe('Alert details', () => {
|
||||||
describe('when alert is null', () => {
|
describe('when alert is null', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mountComponent({ alert: null });
|
mountComponent({ data: { alert: null } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows an empty state', () => {
|
it('shows an empty state', () => {
|
||||||
|
@ -102,5 +102,17 @@ describe('AlertDetails', () => {
|
||||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('error state', () => {
|
||||||
|
it('displays a error state correctly', () => {
|
||||||
|
mountComponent({ data: { errored: true } });
|
||||||
|
expect(wrapper.find(GlAlert).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not display an error when dismissed', () => {
|
||||||
|
mountComponent({ data: { errored: true, isErrorDismissed: true } });
|
||||||
|
expect(wrapper.find(GlAlert).exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,6 +96,13 @@ describe('Ci variable modal', () => {
|
||||||
findModal().vm.$emit('hidden');
|
findModal().vm.$emit('hidden');
|
||||||
expect(store.dispatch).toHaveBeenCalledWith('clearModal');
|
expect(store.dispatch).toHaveBeenCalledWith('clearModal');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should dispatch setVariableProtected when admin settings are configured to protect variables', () => {
|
||||||
|
store.state.isProtectedByDefault = true;
|
||||||
|
findModal().vm.$emit('shown');
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith('setVariableProtected');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Editing a variable', () => {
|
describe('Editing a variable', () => {
|
||||||
|
|
|
@ -75,6 +75,16 @@ describe('CI variable list store actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setVariableProtected', () => {
|
||||||
|
it('commits SET_VARIABLE_PROTECTED mutation', () => {
|
||||||
|
testAction(actions.setVariableProtected, {}, {}, [
|
||||||
|
{
|
||||||
|
type: types.SET_VARIABLE_PROTECTED,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('deleteVariable', () => {
|
describe('deleteVariable', () => {
|
||||||
it('dispatch correct actions on successful deleted variable', done => {
|
it('dispatch correct actions on successful deleted variable', done => {
|
||||||
mock.onPatch(state.endpoint).reply(200);
|
mock.onPatch(state.endpoint).reply(200);
|
||||||
|
|
|
@ -97,4 +97,12 @@ describe('CI variable list mutations', () => {
|
||||||
expect(stateCopy.environments).toEqual(['dev', 'production', 'staging']);
|
expect(stateCopy.environments).toEqual(['dev', 'production', 'staging']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('SET_VARIABLE_PROTECTED', () => {
|
||||||
|
it('should set protected value to true', () => {
|
||||||
|
mutations[types.SET_VARIABLE_PROTECTED](stateCopy);
|
||||||
|
|
||||||
|
expect(stateCopy.variable.protected).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
import createEventHub from '~/helpers/event_hub_factory';
|
import createEventHub from '~/helpers/event_hub_factory';
|
||||||
import mitt from 'mitt';
|
|
||||||
|
|
||||||
jest.mock('mitt');
|
|
||||||
|
|
||||||
mitt.mockReturnValue({
|
|
||||||
on: () => {},
|
|
||||||
off: () => {},
|
|
||||||
emit: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('event bus factory', () => {
|
describe('event bus factory', () => {
|
||||||
let eventBus;
|
let eventBus;
|
||||||
|
@ -20,13 +11,31 @@ describe('event bus factory', () => {
|
||||||
eventBus = null;
|
eventBus = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('underlying module', () => {
|
||||||
|
let mitt;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
jest.mock('mitt');
|
||||||
|
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
mitt = require('mitt');
|
||||||
|
mitt.mockReturnValue(() => ({}));
|
||||||
|
|
||||||
|
const createEventHubActual = jest.requireActual('~/helpers/event_hub_factory').default;
|
||||||
|
eventBus = createEventHubActual();
|
||||||
|
});
|
||||||
|
|
||||||
it('creates an emitter', () => {
|
it('creates an emitter', () => {
|
||||||
expect(mitt).toHaveBeenCalled();
|
expect(mitt).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance', () => {
|
||||||
it.each`
|
it.each`
|
||||||
method
|
method
|
||||||
${'on'}
|
${'on'}
|
||||||
|
${'once'}
|
||||||
${'off'}
|
${'off'}
|
||||||
${'emit'}
|
${'emit'}
|
||||||
`('binds $$method to $method ', ({ method }) => {
|
`('binds $$method to $method ', ({ method }) => {
|
||||||
|
@ -34,3 +43,52 @@ describe('event bus factory', () => {
|
||||||
expect(eventBus[method]).toBe(eventBus[`$${method}`]);
|
expect(eventBus[method]).toBe(eventBus[`$${method}`]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('once', () => {
|
||||||
|
const event = 'foobar';
|
||||||
|
let handler;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(eventBus, 'on');
|
||||||
|
jest.spyOn(eventBus, 'off');
|
||||||
|
handler = jest.fn();
|
||||||
|
eventBus.once(event, handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls on internally', () => {
|
||||||
|
expect(eventBus.on).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handler when event is emitted', () => {
|
||||||
|
eventBus.emit(event);
|
||||||
|
expect(handler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls off when event is emitted', () => {
|
||||||
|
eventBus.emit(event);
|
||||||
|
expect(eventBus.off).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the handler only once when event is emitted multiple times', () => {
|
||||||
|
eventBus.emit(event);
|
||||||
|
eventBus.emit(event);
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the handler thows an error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
handler = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error();
|
||||||
|
});
|
||||||
|
eventBus.once(event, handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls off when event is emitted', () => {
|
||||||
|
expect(() => {
|
||||||
|
eventBus.emit(event);
|
||||||
|
}).toThrow();
|
||||||
|
expect(eventBus.off).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { GlAlert } from '@gitlab/ui';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import IssuableListRootApp from '~/issuables_list/components/issuable_list_root_app.vue';
|
||||||
|
|
||||||
|
const mountComponent = ({
|
||||||
|
canEdit = true,
|
||||||
|
isAlertShowing = true,
|
||||||
|
isInProgress = false,
|
||||||
|
isJiraConfigured = true,
|
||||||
|
} = {}) =>
|
||||||
|
shallowMount(IssuableListRootApp, {
|
||||||
|
propsData: {
|
||||||
|
canEdit,
|
||||||
|
isJiraConfigured,
|
||||||
|
projectPath: 'gitlab-org/gitlab-test',
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isAlertShowing,
|
||||||
|
jiraImport: {
|
||||||
|
isInProgress,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IssuableListRootApp', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when Jira import is in progress', () => {
|
||||||
|
it('shows an alert that tells the user a Jira import is in progress', () => {
|
||||||
|
wrapper = mountComponent({
|
||||||
|
isInProgress: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find(GlAlert).text()).toBe(
|
||||||
|
'Import in progress. Refresh page to see newly added issues.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when Jira import is not in progress', () => {
|
||||||
|
it('does not show an alert', () => {
|
||||||
|
wrapper = mountComponent();
|
||||||
|
|
||||||
|
expect(wrapper.contains(GlAlert)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('alert message', () => {
|
||||||
|
it('is hidden when dismissed', () => {
|
||||||
|
wrapper = mountComponent({
|
||||||
|
isInProgress: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.contains(GlAlert)).toBe(true);
|
||||||
|
|
||||||
|
wrapper.find(GlAlert).vm.$emit('dismiss');
|
||||||
|
|
||||||
|
return Vue.nextTick(() => {
|
||||||
|
expect(wrapper.contains(GlAlert)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -221,6 +221,19 @@ describe('Issuable output', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not redirect if issue has not moved and user has switched tabs', () => {
|
||||||
|
jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
web_url: '',
|
||||||
|
confidential: vm.isConfidential,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return vm.updateIssuable().then(() => {
|
||||||
|
expect(visitUrl).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('redirects if returned web_url has changed', () => {
|
it('redirects if returned web_url has changed', () => {
|
||||||
jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
|
jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -6,8 +6,22 @@ describe GitlabSchema.types['SnippetBlob'] do
|
||||||
it 'has the correct fields' do
|
it 'has the correct fields' do
|
||||||
expected_fields = [:rich_data, :plain_data,
|
expected_fields = [:rich_data, :plain_data,
|
||||||
:raw_path, :size, :binary, :name, :path,
|
:raw_path, :size, :binary, :name, :path,
|
||||||
:simple_viewer, :rich_viewer, :mode]
|
:simple_viewer, :rich_viewer, :mode, :external_storage,
|
||||||
|
:rendered_as_text]
|
||||||
|
|
||||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
specify { expect(described_class.fields['richData'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['plainData'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['rawPath'].type).to be_non_null }
|
||||||
|
specify { expect(described_class.fields['size'].type).to be_non_null }
|
||||||
|
specify { expect(described_class.fields['binary'].type).to be_non_null }
|
||||||
|
specify { expect(described_class.fields['name'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['path'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['simpleViewer'].type).to be_non_null }
|
||||||
|
specify { expect(described_class.fields['richViewer'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['mode'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['externalStorage'].type).not_to be_non_null }
|
||||||
|
specify { expect(described_class.fields['renderedAsText'].type).to be_non_null }
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,6 +17,10 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
request.update_param(key, value)
|
request.update_param(key, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_header(key, value)
|
||||||
|
env[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
describe '#find_user_from_warden' do
|
describe '#find_user_from_warden' do
|
||||||
context 'with CSRF token' do
|
context 'with CSRF token' do
|
||||||
before do
|
before do
|
||||||
|
@ -31,7 +35,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'with valid credentials' do
|
context 'with valid credentials' do
|
||||||
it 'returns the user' do
|
it 'returns the user' do
|
||||||
env['warden'] = double("warden", authenticate: user)
|
set_header('warden', double("warden", authenticate: user))
|
||||||
|
|
||||||
expect(find_user_from_warden).to eq user
|
expect(find_user_from_warden).to eq user
|
||||||
end
|
end
|
||||||
|
@ -41,7 +45,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
context 'without CSRF token' do
|
context 'without CSRF token' do
|
||||||
it 'returns nil' do
|
it 'returns nil' do
|
||||||
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false)
|
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false)
|
||||||
env['warden'] = double("warden", authenticate: user)
|
set_header('warden', double("warden", authenticate: user))
|
||||||
|
|
||||||
expect(find_user_from_warden).to be_nil
|
expect(find_user_from_warden).to be_nil
|
||||||
end
|
end
|
||||||
|
@ -51,8 +55,8 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
describe '#find_user_from_feed_token' do
|
describe '#find_user_from_feed_token' do
|
||||||
context 'when the request format is atom' do
|
context 'when the request format is atom' do
|
||||||
before do
|
before do
|
||||||
env['SCRIPT_NAME'] = 'url.atom'
|
set_header('SCRIPT_NAME', 'url.atom')
|
||||||
env['HTTP_ACCEPT'] = 'application/atom+xml'
|
set_header('HTTP_ACCEPT', 'application/atom+xml')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when feed_token param is provided' do
|
context 'when feed_token param is provided' do
|
||||||
|
@ -94,7 +98,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'when the request format is not atom' do
|
context 'when the request format is not atom' do
|
||||||
it 'returns nil' do
|
it 'returns nil' do
|
||||||
env['SCRIPT_NAME'] = 'json'
|
set_header('SCRIPT_NAME', 'json')
|
||||||
|
|
||||||
set_param(:feed_token, user.feed_token)
|
set_param(:feed_token, user.feed_token)
|
||||||
|
|
||||||
|
@ -104,7 +108,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'when the request format is empty' do
|
context 'when the request format is empty' do
|
||||||
it 'the method call does not modify the original value' do
|
it 'the method call does not modify the original value' do
|
||||||
env['SCRIPT_NAME'] = 'url.atom'
|
set_header('SCRIPT_NAME', 'url.atom')
|
||||||
|
|
||||||
env.delete('action_dispatch.request.formats')
|
env.delete('action_dispatch.request.formats')
|
||||||
|
|
||||||
|
@ -118,7 +122,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
describe '#find_user_from_static_object_token' do
|
describe '#find_user_from_static_object_token' do
|
||||||
shared_examples 'static object request' do
|
shared_examples 'static object request' do
|
||||||
before do
|
before do
|
||||||
env['SCRIPT_NAME'] = path
|
set_header('SCRIPT_NAME', path)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when token header param is present' do
|
context 'when token header param is present' do
|
||||||
|
@ -174,7 +178,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'when request format is not archive nor blob' do
|
context 'when request format is not archive nor blob' do
|
||||||
before do
|
before do
|
||||||
env['script_name'] = 'url'
|
set_header('script_name', 'url')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil' do
|
it 'returns nil' do
|
||||||
|
@ -183,11 +187,46 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#deploy_token_from_request' do
|
||||||
|
let_it_be(:deploy_token) { create(:deploy_token) }
|
||||||
|
let_it_be(:route_authentication_setting) { { deploy_token_allowed: true } }
|
||||||
|
|
||||||
|
subject { deploy_token_from_request }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
|
||||||
|
shared_examples 'an unauthenticated route' do
|
||||||
|
context 'when route is not allowed to use deploy_tokens' do
|
||||||
|
let(:route_authentication_setting) { { deploy_token_allowed: false } }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with deploy token headers' do
|
||||||
|
before do
|
||||||
|
set_header(described_class::DEPLOY_TOKEN_HEADER, deploy_token.token)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq deploy_token }
|
||||||
|
|
||||||
|
it_behaves_like 'an unauthenticated route'
|
||||||
|
|
||||||
|
context 'with incorrect token' do
|
||||||
|
before do
|
||||||
|
set_header(described_class::DEPLOY_TOKEN_HEADER, 'invalid_token')
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#find_user_from_access_token' do
|
describe '#find_user_from_access_token' do
|
||||||
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
env['SCRIPT_NAME'] = 'url.atom'
|
set_header('SCRIPT_NAME', 'url.atom')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil if no access_token present' do
|
it 'returns nil if no access_token present' do
|
||||||
|
@ -196,13 +235,13 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'when validate_access_token! returns valid' do
|
context 'when validate_access_token! returns valid' do
|
||||||
it 'returns user' do
|
it 'returns user' do
|
||||||
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
|
||||||
|
|
||||||
expect(find_user_from_access_token).to eq user
|
expect(find_user_from_access_token).to eq user
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns exception if token has no user' do
|
it 'returns exception if token has no user' do
|
||||||
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
|
||||||
allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
|
allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
|
||||||
|
|
||||||
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||||
|
@ -211,7 +250,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'with OAuth headers' do
|
context 'with OAuth headers' do
|
||||||
it 'returns user' do
|
it 'returns user' do
|
||||||
env['HTTP_AUTHORIZATION'] = "Bearer #{personal_access_token.token}"
|
set_header('HTTP_AUTHORIZATION', "Bearer #{personal_access_token.token}")
|
||||||
|
|
||||||
expect(find_user_from_access_token).to eq user
|
expect(find_user_from_access_token).to eq user
|
||||||
end
|
end
|
||||||
|
@ -228,7 +267,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns exception if token has no user' do
|
it 'returns exception if token has no user' do
|
||||||
|
@ -252,19 +291,19 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the user for RSS requests' do
|
it 'returns the user for RSS requests' do
|
||||||
env['SCRIPT_NAME'] = 'url.atom'
|
set_header('SCRIPT_NAME', 'url.atom')
|
||||||
|
|
||||||
expect(find_user_from_web_access_token(:rss)).to eq(user)
|
expect(find_user_from_web_access_token(:rss)).to eq(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the user for ICS requests' do
|
it 'returns the user for ICS requests' do
|
||||||
env['SCRIPT_NAME'] = 'url.ics'
|
set_header('SCRIPT_NAME', 'url.ics')
|
||||||
|
|
||||||
expect(find_user_from_web_access_token(:ics)).to eq(user)
|
expect(find_user_from_web_access_token(:ics)).to eq(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the user for API requests' do
|
it 'returns the user for API requests' do
|
||||||
env['SCRIPT_NAME'] = '/api/endpoint'
|
set_header('SCRIPT_NAME', '/api/endpoint')
|
||||||
|
|
||||||
expect(find_user_from_web_access_token(:api)).to eq(user)
|
expect(find_user_from_web_access_token(:api)).to eq(user)
|
||||||
end
|
end
|
||||||
|
@ -274,12 +313,12 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
env['SCRIPT_NAME'] = 'url.atom'
|
set_header('SCRIPT_NAME', 'url.atom')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'passed as header' do
|
context 'passed as header' do
|
||||||
it 'returns token if valid personal_access_token' do
|
it 'returns token if valid personal_access_token' do
|
||||||
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
|
||||||
|
|
||||||
expect(find_personal_access_token).to eq personal_access_token
|
expect(find_personal_access_token).to eq personal_access_token
|
||||||
end
|
end
|
||||||
|
@ -298,7 +337,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns exception if invalid personal_access_token' do
|
it 'returns exception if invalid personal_access_token' do
|
||||||
env[described_class::PRIVATE_TOKEN_HEADER] = 'invalid_token'
|
set_header(described_class::PRIVATE_TOKEN_HEADER, 'invalid_token')
|
||||||
|
|
||||||
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||||
end
|
end
|
||||||
|
@ -310,7 +349,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'passed as header' do
|
context 'passed as header' do
|
||||||
it 'returns token if valid oauth_access_token' do
|
it 'returns token if valid oauth_access_token' do
|
||||||
env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}"
|
set_header('HTTP_AUTHORIZATION', "Bearer #{token.token}")
|
||||||
|
|
||||||
expect(find_oauth_access_token.token).to eq token.token
|
expect(find_oauth_access_token.token).to eq token.token
|
||||||
end
|
end
|
||||||
|
@ -329,7 +368,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns exception if invalid oauth_access_token' do
|
it 'returns exception if invalid oauth_access_token' do
|
||||||
env['HTTP_AUTHORIZATION'] = "Bearer invalid_token"
|
set_header('HTTP_AUTHORIZATION', "Bearer invalid_token")
|
||||||
|
|
||||||
expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||||
end
|
end
|
||||||
|
@ -337,7 +376,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
describe '#find_personal_access_token_from_http_basic_auth' do
|
describe '#find_personal_access_token_from_http_basic_auth' do
|
||||||
def auth_header_with(token)
|
def auth_header_with(token)
|
||||||
env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('username', token)
|
set_header('HTTP_AUTHORIZATION', ActionController::HttpAuthentication::Basic.encode_credentials('username', token))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'access token is valid' do
|
context 'access token is valid' do
|
||||||
|
@ -389,7 +428,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_auth(username, password)
|
def set_auth(username, password)
|
||||||
env['HTTP_AUTHORIZATION'] = basic_http_auth(username, password)
|
set_header('HTTP_AUTHORIZATION', basic_http_auth(username, password))
|
||||||
end
|
end
|
||||||
|
|
||||||
subject { find_user_from_basic_auth_job }
|
subject { find_user_from_basic_auth_job }
|
||||||
|
@ -502,20 +541,20 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'when the job token is in the headers' do
|
context 'when the job token is in the headers' do
|
||||||
it 'returns the user if valid job token' do
|
it 'returns the user if valid job token' do
|
||||||
env[described_class::JOB_TOKEN_HEADER] = job.token
|
set_header(described_class::JOB_TOKEN_HEADER, job.token)
|
||||||
|
|
||||||
is_expected.to eq(user)
|
is_expected.to eq(user)
|
||||||
expect(@current_authenticated_job).to eq(job)
|
expect(@current_authenticated_job).to eq(job)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil without job token' do
|
it 'returns nil without job token' do
|
||||||
env[described_class::JOB_TOKEN_HEADER] = ''
|
set_header(described_class::JOB_TOKEN_HEADER, '')
|
||||||
|
|
||||||
is_expected.to be_nil
|
is_expected.to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns exception if invalid job token' do
|
it 'returns exception if invalid job token' do
|
||||||
env[described_class::JOB_TOKEN_HEADER] = 'invalid token'
|
set_header(described_class::JOB_TOKEN_HEADER, 'invalid token')
|
||||||
|
|
||||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||||
end
|
end
|
||||||
|
@ -524,7 +563,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
let(:route_authentication_setting) { { job_token_allowed: false } }
|
let(:route_authentication_setting) { { job_token_allowed: false } }
|
||||||
|
|
||||||
it 'sets current_user to nil' do
|
it 'sets current_user to nil' do
|
||||||
env[described_class::JOB_TOKEN_HEADER] = job.token
|
set_header(described_class::JOB_TOKEN_HEADER, job.token)
|
||||||
|
|
||||||
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
|
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
|
||||||
|
|
||||||
|
@ -586,7 +625,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'with API requests' do
|
context 'with API requests' do
|
||||||
before do
|
before do
|
||||||
env['SCRIPT_NAME'] = '/api/endpoint'
|
set_header('SCRIPT_NAME', '/api/endpoint')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the runner if token is valid' do
|
it 'returns the runner if token is valid' do
|
||||||
|
@ -614,7 +653,7 @@ describe Gitlab::Auth::AuthFinders do
|
||||||
|
|
||||||
context 'without API requests' do
|
context 'without API requests' do
|
||||||
before do
|
before do
|
||||||
env['SCRIPT_NAME'] = 'url.ics'
|
set_header('SCRIPT_NAME', 'url.ics')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil if token is valid' do
|
it 'returns nil if token is valid' do
|
||||||
|
|
|
@ -64,7 +64,7 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
|
||||||
shared_examples 'logging of relations creation' do
|
shared_examples 'logging of relations creation' do
|
||||||
context 'when log_import_export_relation_creation feature flag is enabled' do
|
context 'when log_import_export_relation_creation feature flag is enabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(log_import_export_relation_creation: { enabled: true, thing: group })
|
stub_feature_flags(log_import_export_relation_creation: group)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'logs top-level relation creation' do
|
it 'logs top-level relation creation' do
|
||||||
|
@ -79,7 +79,7 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
|
||||||
|
|
||||||
context 'when log_import_export_relation_creation feature flag is disabled' do
|
context 'when log_import_export_relation_creation feature flag is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(log_import_export_relation_creation: { enabled: false, thing: group })
|
stub_feature_flags(log_import_export_relation_creation: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not log top-level relation creation' do
|
it 'does not log top-level relation creation' do
|
||||||
|
|
|
@ -37,7 +37,7 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
|
||||||
|
|
||||||
describe '#sample_memory_usage' do
|
describe '#sample_memory_usage' do
|
||||||
it 'adds a metric containing the memory usage' do
|
it 'adds a metric containing the memory usage' do
|
||||||
expect(Gitlab::Metrics::System).to receive(:memory_usage)
|
expect(Gitlab::Metrics::System).to receive(:memory_usage_rss)
|
||||||
.and_return(9000)
|
.and_return(9000)
|
||||||
|
|
||||||
expect(sampler).to receive(:add_metric)
|
expect(sampler).to receive(:add_metric)
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe Gitlab::Metrics::Samplers::RubySampler do
|
||||||
|
|
||||||
describe '#sample' do
|
describe '#sample' do
|
||||||
it 'adds a metric containing the process resident memory bytes' do
|
it 'adds a metric containing the process resident memory bytes' do
|
||||||
expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
|
expect(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return(9000)
|
||||||
|
|
||||||
expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)
|
expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)
|
||||||
|
|
||||||
|
|
|
@ -64,11 +64,11 @@ describe Gitlab::Metrics::System do
|
||||||
SNIP
|
SNIP
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.memory_usage' do
|
describe '.memory_usage_rss' do
|
||||||
it "returns the process' resident set size (RSS) in bytes" do
|
it "returns the process' resident set size (RSS) in bytes" do
|
||||||
mock_existing_proc_file('/proc/self/status', proc_status)
|
mock_existing_proc_file('/proc/self/status', proc_status)
|
||||||
|
|
||||||
expect(described_class.memory_usage).to eq(2527232)
|
expect(described_class.memory_usage_rss).to eq(2527232)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,9 +103,9 @@ describe Gitlab::Metrics::System do
|
||||||
mock_missing_proc_file
|
mock_missing_proc_file
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.memory_usage' do
|
describe '.memory_usage_rss' do
|
||||||
it 'returns 0' do
|
it 'returns 0' do
|
||||||
expect(described_class.memory_usage).to eq(0)
|
expect(described_class.memory_usage_rss).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -28,17 +28,13 @@ describe Gitlab::Tracking do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'enables features using feature flags' do
|
it 'enables features using feature flags' do
|
||||||
stub_feature_flags(additional_snowplow_tracking: true)
|
stub_feature_flags(additional_snowplow_tracking: :__group__)
|
||||||
allow(Feature).to receive(:enabled?).with(
|
|
||||||
:additional_snowplow_tracking,
|
|
||||||
'_group_'
|
|
||||||
).and_return(false)
|
|
||||||
addition_feature_fields = {
|
addition_feature_fields = {
|
||||||
formTracking: false,
|
formTracking: false,
|
||||||
linkClickTracking: false
|
linkClickTracking: false
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(subject.snowplow_options('_group_')).to include(addition_feature_fields)
|
expect(subject.snowplow_options(:_group_)).to include(addition_feature_fields)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,11 +76,7 @@ describe Gitlab::WikiPages::FrontMatterParser do
|
||||||
let(:raw_content) { with_front_matter }
|
let(:raw_content) { with_front_matter }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => false)
|
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => gate)
|
||||||
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => {
|
|
||||||
enabled: true,
|
|
||||||
thing: gate
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
|
|
|
@ -3636,6 +3636,24 @@ describe Project do
|
||||||
expect(projects).to contain_exactly(public_project)
|
expect(projects).to contain_exactly(public_project)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with deploy token users' do
|
||||||
|
let_it_be(:private_project) { create(:project, :private) }
|
||||||
|
|
||||||
|
subject { described_class.all.public_or_visible_to_user(user) }
|
||||||
|
|
||||||
|
context 'deploy token user without project' do
|
||||||
|
let_it_be(:user) { create(:deploy_token) }
|
||||||
|
|
||||||
|
it { is_expected.to eq [] }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'deploy token user with project' do
|
||||||
|
let_it_be(:user) { create(:deploy_token, projects: [private_project]) }
|
||||||
|
|
||||||
|
it { is_expected.to include(private_project) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.ids_with_issuables_available_for' do
|
describe '.ids_with_issuables_available_for' do
|
||||||
|
|
|
@ -16,10 +16,7 @@ describe WikiPage do
|
||||||
end
|
end
|
||||||
|
|
||||||
def enable_front_matter_for(thing)
|
def enable_front_matter_for(thing)
|
||||||
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => {
|
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => thing)
|
||||||
thing: thing,
|
|
||||||
enabled: true
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.group_by_directory' do
|
describe '.group_by_directory' do
|
||||||
|
|
|
@ -691,4 +691,28 @@ describe ProjectPolicy do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'deploy token access' do
|
||||||
|
let!(:project_deploy_token) do
|
||||||
|
create(:project_deploy_token, project: project, deploy_token: deploy_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { described_class.new(deploy_token, project) }
|
||||||
|
|
||||||
|
context 'a deploy token with read_package_registry scope' do
|
||||||
|
let(:deploy_token) { create(:deploy_token, read_package_registry: true) }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(:read_package) }
|
||||||
|
it { is_expected.to be_allowed(:read_project) }
|
||||||
|
it { is_expected.to be_disallowed(:create_package) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'a deploy token with write_package_registry scope' do
|
||||||
|
let(:deploy_token) { create(:deploy_token, write_package_registry: true) }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(:create_package) }
|
||||||
|
it { is_expected.to be_allowed(:read_project) }
|
||||||
|
it { is_expected.to be_disallowed(:destroy_package) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -452,7 +452,7 @@ describe API::Internal::Base do
|
||||||
|
|
||||||
context 'when gitaly_upload_pack_filter feature flag is disabled' do
|
context 'when gitaly_upload_pack_filter feature flag is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(gitaly_upload_pack_filter: { enabled: false, thing: project })
|
stub_feature_flags(gitaly_upload_pack_filter: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns only maxInputSize and not partial clone git config' do
|
it 'returns only maxInputSize and not partial clone git config' do
|
||||||
|
@ -481,7 +481,7 @@ describe API::Internal::Base do
|
||||||
|
|
||||||
context 'when gitaly_upload_pack_filter feature flag is disabled' do
|
context 'when gitaly_upload_pack_filter feature flag is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(gitaly_upload_pack_filter: { enabled: false, thing: project })
|
stub_feature_flags(gitaly_upload_pack_filter: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an empty git config' do
|
it 'returns an empty git config' do
|
||||||
|
|
|
@ -107,7 +107,7 @@ describe API::RemoteMirrors do
|
||||||
|
|
||||||
context 'with the `keep_divergent_refs` feature enabled' do
|
context 'with the `keep_divergent_refs` feature enabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(keep_divergent_refs: { enabled: true, project: project })
|
stub_feature_flags(keep_divergent_refs: project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the `keep_divergent_refs` attribute' do
|
it 'updates the `keep_divergent_refs` attribute' do
|
||||||
|
|
|
@ -163,7 +163,7 @@ describe API::Search do
|
||||||
|
|
||||||
context 'when users search feature is disabled' do
|
context 'when users search feature is disabled' do
|
||||||
before do
|
before do
|
||||||
allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
|
stub_feature_flags(users_search: false)
|
||||||
|
|
||||||
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
|
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
|
||||||
end
|
end
|
||||||
|
@ -336,7 +336,7 @@ describe API::Search do
|
||||||
|
|
||||||
context 'when users search feature is disabled' do
|
context 'when users search feature is disabled' do
|
||||||
before do
|
before do
|
||||||
allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
|
stub_feature_flags(users_search: false)
|
||||||
|
|
||||||
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
|
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
|
||||||
end
|
end
|
||||||
|
@ -501,7 +501,7 @@ describe API::Search do
|
||||||
|
|
||||||
context 'when users search feature is disabled' do
|
context 'when users search feature is disabled' do
|
||||||
before do
|
before do
|
||||||
allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
|
stub_feature_flags(users_search: false)
|
||||||
|
|
||||||
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
|
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
|
||||||
end
|
end
|
||||||
|
|
|
@ -205,6 +205,20 @@ describe Auth::ContainerRegistryAuthenticationService do
|
||||||
|
|
||||||
it_behaves_like 'an inaccessible'
|
it_behaves_like 'an inaccessible'
|
||||||
it_behaves_like 'not a container repository factory'
|
it_behaves_like 'not a container repository factory'
|
||||||
|
|
||||||
|
it 'logs an auth warning' do
|
||||||
|
expect(Gitlab::AuthLogger).to receive(:warn).with(
|
||||||
|
message: 'Denied container registry permissions',
|
||||||
|
scope_type: 'repository',
|
||||||
|
requested_project_path: project.full_path,
|
||||||
|
requested_actions: ['*'],
|
||||||
|
authorized_actions: [],
|
||||||
|
user_id: current_user.id,
|
||||||
|
username: current_user.username
|
||||||
|
)
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'disallow developer to delete images since registry 2.7' do
|
context 'disallow developer to delete images since registry 2.7' do
|
||||||
|
|
|
@ -264,7 +264,7 @@ describe Git::WikiPushService, services: true do
|
||||||
|
|
||||||
context 'but is enabled for a given project' do
|
context 'but is enabled for a given project' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(wiki_events_on_git_push: { enabled: true, thing: project })
|
stub_feature_flags(wiki_events_on_git_push: project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates events' do
|
it 'creates events' do
|
||||||
|
|
|
@ -30,21 +30,14 @@ describe Namespaces::CheckStorageSizeService, '#execute' do
|
||||||
expect(subject).to be_error
|
expect(subject).to be_error
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is successful when disabled for the current group' do
|
|
||||||
stub_feature_flags(namespace_storage_limit: { enabled: false, thing: root_group })
|
|
||||||
|
|
||||||
expect(subject).to be_success
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is successful when feature flag is activated for another group' do
|
it 'is successful when feature flag is activated for another group' do
|
||||||
stub_feature_flags(namespace_storage_limit: false)
|
stub_feature_flags(namespace_storage_limit: create(:group))
|
||||||
stub_feature_flags(namespace_storage_limit: { enabled: true, thing: create(:group) })
|
|
||||||
|
|
||||||
expect(subject).to be_success
|
expect(subject).to be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'errors when feature flag is activated for the current group' do
|
it 'errors when feature flag is activated for the current group' do
|
||||||
stub_feature_flags(namespace_storage_limit: { enabled: true, thing: root_group })
|
stub_feature_flags(namespace_storage_limit: root_group)
|
||||||
|
|
||||||
expect(subject).to be_error
|
expect(subject).to be_error
|
||||||
end
|
end
|
||||||
|
|
|
@ -173,21 +173,19 @@ RSpec.configure do |config|
|
||||||
# Enable all features by default for testing
|
# Enable all features by default for testing
|
||||||
allow(Feature).to receive(:enabled?) { true }
|
allow(Feature).to receive(:enabled?) { true }
|
||||||
|
|
||||||
enabled = example.metadata[:enable_rugged].present?
|
enable_rugged = example.metadata[:enable_rugged].present?
|
||||||
|
|
||||||
# Disable Rugged features by default
|
# Disable Rugged features by default
|
||||||
Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
|
Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
|
||||||
allow(Feature).to receive(:enabled?).with(flag).and_return(enabled)
|
stub_feature_flags(flag => enable_rugged)
|
||||||
end
|
end
|
||||||
|
|
||||||
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enabled)
|
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
|
||||||
|
|
||||||
# The following can be removed when we remove the staged rollout strategy
|
# The following can be removed when we remove the staged rollout strategy
|
||||||
# and we can just enable it using instance wide settings
|
# and we can just enable it using instance wide settings
|
||||||
# (ie. ApplicationSetting#auto_devops_enabled)
|
# (ie. ApplicationSetting#auto_devops_enabled)
|
||||||
allow(Feature).to receive(:enabled?)
|
stub_feature_flags(force_autodevops_on_by_default: false)
|
||||||
.with(:force_autodevops_on_by_default, anything)
|
|
||||||
.and_return(false)
|
|
||||||
|
|
||||||
# Enable Marginalia feature for all specs in the test suite.
|
# Enable Marginalia feature for all specs in the test suite.
|
||||||
allow(Gitlab::Marginalia).to receive(:cached_feature_enabled?).and_return(true)
|
allow(Gitlab::Marginalia).to receive(:cached_feature_enabled?).and_return(true)
|
||||||
|
@ -196,12 +194,8 @@ RSpec.configure do |config|
|
||||||
# is feature-complete and can be made default in place
|
# is feature-complete and can be made default in place
|
||||||
# of older sidebar.
|
# of older sidebar.
|
||||||
# See https://gitlab.com/groups/gitlab-org/-/epics/1863
|
# See https://gitlab.com/groups/gitlab-org/-/epics/1863
|
||||||
allow(Feature).to receive(:enabled?)
|
stub_feature_flags(vue_issuable_sidebar: false)
|
||||||
.with(:vue_issuable_sidebar, anything)
|
stub_feature_flags(vue_issuable_epic_sidebar: false)
|
||||||
.and_return(false)
|
|
||||||
allow(Feature).to receive(:enabled?)
|
|
||||||
.with(:vue_issuable_epic_sidebar, anything)
|
|
||||||
.and_return(false)
|
|
||||||
|
|
||||||
# Stub these calls due to being expensive operations
|
# Stub these calls due to being expensive operations
|
||||||
# It can be reenabled for specific tests via:
|
# It can be reenabled for specific tests via:
|
||||||
|
|
|
@ -9,23 +9,27 @@ module StubFeatureFlags
|
||||||
# Examples
|
# Examples
|
||||||
# - `stub_feature_flags(ci_live_trace: false)` ... Disable `ci_live_trace`
|
# - `stub_feature_flags(ci_live_trace: false)` ... Disable `ci_live_trace`
|
||||||
# feature flag globally.
|
# feature flag globally.
|
||||||
# - `stub_feature_flags(ci_live_trace: { enabled: false, thing: project })` ...
|
# - `stub_feature_flags(ci_live_trace: project)` ...
|
||||||
# Disable `ci_live_trace` feature flag on the specified project.
|
# - `stub_feature_flags(ci_live_trace: [project1, project2])` ...
|
||||||
|
# Enable `ci_live_trace` feature flag only on the specified projects.
|
||||||
def stub_feature_flags(features)
|
def stub_feature_flags(features)
|
||||||
features.each do |feature_name, option|
|
features.each do |feature_name, actors|
|
||||||
if option.is_a?(Hash)
|
allow(Feature).to receive(:enabled?).with(feature_name, any_args).and_return(false)
|
||||||
enabled, thing = option.values_at(:enabled, :thing)
|
allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args).and_return(false)
|
||||||
else
|
|
||||||
enabled = option
|
|
||||||
thing = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if thing
|
Array(actors).each do |actor|
|
||||||
allow(Feature).to receive(:enabled?).with(feature_name, thing, any_args) { enabled }
|
raise ArgumentError, "actor cannot be Hash" if actor.is_a?(Hash)
|
||||||
allow(Feature).to receive(:enabled?).with(feature_name.to_s, thing, any_args) { enabled }
|
|
||||||
|
case actor
|
||||||
|
when false, true
|
||||||
|
allow(Feature).to receive(:enabled?).with(feature_name, any_args).and_return(actor)
|
||||||
|
allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args).and_return(actor)
|
||||||
|
when nil, ActiveRecord::Base, Symbol, RSpec::Mocks::Double
|
||||||
|
allow(Feature).to receive(:enabled?).with(feature_name, actor, any_args).and_return(true)
|
||||||
|
allow(Feature).to receive(:enabled?).with(feature_name.to_s, actor, any_args).and_return(true)
|
||||||
else
|
else
|
||||||
allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled }
|
raise ArgumentError, "#stub_feature_flags accepts only `nil`, `true`, `false`, `ActiveRecord::Base` or `Symbol` as actors"
|
||||||
allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled }
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -60,7 +60,7 @@ RSpec.shared_examples 'diff file entity' do
|
||||||
|
|
||||||
context 'when the `single_mr_diff_view` feature is disabled' do
|
context 'when the `single_mr_diff_view` feature is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(single_mr_diff_view: { enabled: false, thing: project })
|
stub_feature_flags(single_mr_diff_view: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'contains both kinds of diffs' do
|
it 'contains both kinds of diffs' do
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe StubFeatureFlags do
|
||||||
|
before do
|
||||||
|
# reset stub introduced by `stub_feature_flags`
|
||||||
|
allow(Feature).to receive(:enabled?).and_call_original
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'if not stubbed' do
|
||||||
|
it 'features are disabled by default' do
|
||||||
|
expect(Feature.enabled?(:test_feature)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#stub_feature_flags' do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
let(:feature_name) { :test_feature }
|
||||||
|
|
||||||
|
context 'when checking global state' do
|
||||||
|
where(:feature_actors, :expected_result) do
|
||||||
|
false | false
|
||||||
|
true | true
|
||||||
|
:A | false
|
||||||
|
%i[A] | false
|
||||||
|
%i[A B] | false
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(feature_name => feature_actors)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(Feature.enabled?(feature_name)).to eq(expected_result) }
|
||||||
|
it { expect(Feature.disabled?(feature_name)).not_to eq(expected_result) }
|
||||||
|
|
||||||
|
context 'default_enabled does not impact feature state' do
|
||||||
|
it { expect(Feature.enabled?(feature_name, default_enabled: true)).to eq(expected_result) }
|
||||||
|
it { expect(Feature.disabled?(feature_name, default_enabled: true)).not_to eq(expected_result) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when checking scoped state' do
|
||||||
|
where(:feature_actors, :tested_actor, :expected_result) do
|
||||||
|
false | nil | false
|
||||||
|
true | nil | true
|
||||||
|
false | :A | false
|
||||||
|
true | :A | true
|
||||||
|
:A | nil | false
|
||||||
|
:A | :A | true
|
||||||
|
:A | :B | false
|
||||||
|
%i[A] | nil | false
|
||||||
|
%i[A] | :A | true
|
||||||
|
%i[A] | :B | false
|
||||||
|
%i[A B] | nil | false
|
||||||
|
%i[A B] | :A | true
|
||||||
|
%i[A B] | :B | true
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(feature_name => feature_actors)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(Feature.enabled?(feature_name, tested_actor)).to eq(expected_result) }
|
||||||
|
it { expect(Feature.disabled?(feature_name, tested_actor)).not_to eq(expected_result) }
|
||||||
|
|
||||||
|
context 'default_enabled does not impact feature state' do
|
||||||
|
it { expect(Feature.enabled?(feature_name, tested_actor, default_enabled: true)).to eq(expected_result) }
|
||||||
|
it { expect(Feature.disabled?(feature_name, tested_actor, default_enabled: true)).not_to eq(expected_result) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'type handling' do
|
||||||
|
context 'raises error' do
|
||||||
|
where(:feature_actors) do
|
||||||
|
['string', 1, 1.0, OpenStruct.new]
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
subject { stub_feature_flags(feature_name => feature_actors) }
|
||||||
|
|
||||||
|
it { expect { subject }.to raise_error(ArgumentError, /accepts only/) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'does not raise error' do
|
||||||
|
where(:feature_actors) do
|
||||||
|
[true, false, nil, :symbol, double, User.new]
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
subject { stub_feature_flags(feature_name => feature_actors) }
|
||||||
|
|
||||||
|
it { expect { subject }.not_to raise_error }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'subsquent run changes state' do
|
||||||
|
# enable FF only on A
|
||||||
|
stub_feature_flags(test_feature: %i[A])
|
||||||
|
expect(Feature.enabled?(:test_feature)).to eq(false)
|
||||||
|
expect(Feature.enabled?(:test_feature, :A)).to eq(true)
|
||||||
|
expect(Feature.enabled?(:test_feature, :B)).to eq(false)
|
||||||
|
|
||||||
|
# enable FF only on B
|
||||||
|
stub_feature_flags(test_feature: %i[B])
|
||||||
|
expect(Feature.enabled?(:test_feature)).to eq(false)
|
||||||
|
expect(Feature.enabled?(:test_feature, :A)).to eq(false)
|
||||||
|
expect(Feature.enabled?(:test_feature, :B)).to eq(true)
|
||||||
|
|
||||||
|
# enable FF on all
|
||||||
|
stub_feature_flags(test_feature: true)
|
||||||
|
expect(Feature.enabled?(:test_feature)).to eq(true)
|
||||||
|
expect(Feature.enabled?(:test_feature, :A)).to eq(true)
|
||||||
|
expect(Feature.enabled?(:test_feature, :B)).to eq(true)
|
||||||
|
|
||||||
|
# disable FF on all
|
||||||
|
stub_feature_flags(test_feature: false)
|
||||||
|
expect(Feature.enabled?(:test_feature)).to eq(false)
|
||||||
|
expect(Feature.enabled?(:test_feature, :A)).to eq(false)
|
||||||
|
expect(Feature.enabled?(:test_feature, :B)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue