Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
187727e248
commit
146284d119
34 changed files with 210 additions and 730 deletions
|
@ -30,10 +30,11 @@ cache-workhorse:
|
||||||
WEBPACK_REPORT: "false"
|
WEBPACK_REPORT: "false"
|
||||||
script:
|
script:
|
||||||
- !reference [.yarn-install, script]
|
- !reference [.yarn-install, script]
|
||||||
- export GITLAB_ASSETS_HASH=$(bundle exec rake gitlab:assets:hash_sum | tee cached-assets-hash.txt)
|
- export GITLAB_ASSETS_HASH=$(bundle exec rake gitlab:assets:hash_sum)
|
||||||
- source scripts/gitlab_component_helpers.sh
|
- source scripts/gitlab_component_helpers.sh
|
||||||
- 'gitlab_assets_archive_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; }'
|
- 'gitlab_assets_archive_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; }'
|
||||||
- run_timed_command "bin/rake gitlab:assets:compile"
|
- run_timed_command "bin/rake gitlab:assets:compile"
|
||||||
|
- echo -n "${GITLAB_ASSETS_HASH}" > "cached-assets-hash.txt"
|
||||||
- run_timed_command "create_gitlab_assets_package"
|
- run_timed_command "create_gitlab_assets_package"
|
||||||
- run_timed_command "upload_gitlab_assets_package"
|
- run_timed_command "upload_gitlab_assets_package"
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,7 @@
|
||||||
- "GITLAB_WORKHORSE_VERSION"
|
- "GITLAB_WORKHORSE_VERSION"
|
||||||
- "workhorse/**/*"
|
- "workhorse/**/*"
|
||||||
- ".gitlab/ci/workhorse.gitlab-ci.yml"
|
- ".gitlab/ci/workhorse.gitlab-ci.yml"
|
||||||
|
- "spec/support/gitlab-git-test.git/**/*"
|
||||||
|
|
||||||
.yaml-lint-patterns: &yaml-lint-patterns
|
.yaml-lint-patterns: &yaml-lint-patterns
|
||||||
- "*.yml"
|
- "*.yml"
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
getValueStreamStageCounts,
|
getValueStreamStageCounts,
|
||||||
} from '~/api/analytics_api';
|
} from '~/api/analytics_api';
|
||||||
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
|
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import { DEFAULT_VALUE_STREAM, I18N_VSA_ERROR_STAGE_MEDIAN } from '../constants';
|
import { DEFAULT_VALUE_STREAM, I18N_VSA_ERROR_STAGE_MEDIAN } from '../constants';
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
|
@ -97,7 +97,7 @@ export const fetchStageMedians = ({
|
||||||
.then((data) => commit(types.RECEIVE_STAGE_MEDIANS_SUCCESS, data))
|
.then((data) => commit(types.RECEIVE_STAGE_MEDIANS_SUCCESS, data))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
commit(types.RECEIVE_STAGE_MEDIANS_ERROR, error);
|
commit(types.RECEIVE_STAGE_MEDIANS_ERROR, error);
|
||||||
createFlash({ message: I18N_VSA_ERROR_STAGE_MEDIAN });
|
createAlert({ message: I18N_VSA_ERROR_STAGE_MEDIAN });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ export const fetchStageCountValues = ({
|
||||||
.then((data) => commit(types.RECEIVE_STAGE_COUNTS_SUCCESS, data))
|
.then((data) => commit(types.RECEIVE_STAGE_COUNTS_SUCCESS, data))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
commit(types.RECEIVE_STAGE_COUNTS_ERROR, error);
|
commit(types.RECEIVE_STAGE_COUNTS_ERROR, error);
|
||||||
createFlash({
|
createAlert({
|
||||||
message: __('There was an error fetching stage total counts'),
|
message: __('There was an error fetching stage total counts'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Api from '~/api';
|
import Api from '~/api';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import { logError } from '~/lib/logger';
|
import { logError } from '~/lib/logger';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
|
@ -27,7 +27,7 @@ const receiveFreezePeriod = (store, request) => {
|
||||||
dispatch('fetchFreezePeriods');
|
dispatch('fetchFreezePeriods');
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
createFlash({
|
createAlert({
|
||||||
message: __('Error: Unable to create deploy freeze'),
|
message: __('Error: Unable to create deploy freeze'),
|
||||||
});
|
});
|
||||||
dispatch('receiveFreezePeriodError', error);
|
dispatch('receiveFreezePeriodError', error);
|
||||||
|
@ -59,7 +59,7 @@ export const deleteFreezePeriod = ({ state, commit }, { id }) => {
|
||||||
return Api.deleteFreezePeriod(state.projectId, id)
|
return Api.deleteFreezePeriod(state.projectId, id)
|
||||||
.then(() => commit(types.RECEIVE_DELETE_FREEZE_PERIOD_SUCCESS, id))
|
.then(() => commit(types.RECEIVE_DELETE_FREEZE_PERIOD_SUCCESS, id))
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
createFlash({
|
createAlert({
|
||||||
message: __('Error: Unable to delete deploy freeze'),
|
message: __('Error: Unable to delete deploy freeze'),
|
||||||
});
|
});
|
||||||
commit(types.RECEIVE_DELETE_FREEZE_PERIOD_ERROR, id);
|
commit(types.RECEIVE_DELETE_FREEZE_PERIOD_ERROR, id);
|
||||||
|
@ -76,7 +76,7 @@ export const fetchFreezePeriods = ({ commit, state }) => {
|
||||||
commit(types.RECEIVE_FREEZE_PERIODS_SUCCESS, data);
|
commit(types.RECEIVE_FREEZE_PERIODS_SUCCESS, data);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
createFlash({
|
createAlert({
|
||||||
message: __('There was an error fetching the deploy freezes.'),
|
message: __('There was an error fetching the deploy freezes.'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
|
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
|
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
|
||||||
import eventHub from '../eventhub';
|
import eventHub from '../eventhub';
|
||||||
|
@ -93,7 +93,7 @@ export default {
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.store.keys = {};
|
this.store.keys = {};
|
||||||
return createFlash({
|
return createAlert({
|
||||||
message: s__('DeployKeys|Error getting deploy keys'),
|
message: s__('DeployKeys|Error getting deploy keys'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -103,7 +103,7 @@ export default {
|
||||||
.enableKey(deployKey.id)
|
.enableKey(deployKey.id)
|
||||||
.then(this.fetchKeys)
|
.then(this.fetchKeys)
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
createFlash({
|
createAlert({
|
||||||
message: s__('DeployKeys|Error enabling deploy key'),
|
message: s__('DeployKeys|Error enabling deploy key'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -119,7 +119,7 @@ export default {
|
||||||
.then(this.fetchKeys)
|
.then(this.fetchKeys)
|
||||||
.then(hideModal)
|
.then(hideModal)
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
createFlash({
|
createAlert({
|
||||||
message: s__('DeployKeys|Error removing deploy key'),
|
message: s__('DeployKeys|Error removing deploy key'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
import { GlButton, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||||
import { ApolloMutation } from 'vue-apollo';
|
import { ApolloMutation } from 'vue-apollo';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
|
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
|
||||||
import { updateGlobalTodoCount } from '~/vue_shared/components/sidebar/todo_toggle/utils';
|
import { updateGlobalTodoCount } from '~/vue_shared/components/sidebar/todo_toggle/utils';
|
||||||
|
@ -155,7 +155,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
onDone({ data: { createNote } }) {
|
onDone({ data: { createNote } }) {
|
||||||
if (hasErrors(createNote)) {
|
if (hasErrors(createNote)) {
|
||||||
createFlash({ message: ADD_DISCUSSION_COMMENT_ERROR });
|
createAlert({ message: ADD_DISCUSSION_COMMENT_ERROR });
|
||||||
}
|
}
|
||||||
this.discussionComment = '';
|
this.discussionComment = '';
|
||||||
this.hideForm();
|
this.hideForm();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { propertyOf } from 'lodash';
|
import { propertyOf } from 'lodash';
|
||||||
import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
|
import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
|
||||||
import createFlash, { FLASH_TYPES } from '~/flash';
|
import { createAlert, VARIANT_WARNING } from '~/flash';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import { DESIGNS_ROUTE_NAME } from '../router/constants';
|
import { DESIGNS_ROUTE_NAME } from '../router/constants';
|
||||||
import allVersionsMixin from './all_versions';
|
import allVersionsMixin from './all_versions';
|
||||||
|
@ -36,7 +36,7 @@ export default {
|
||||||
},
|
},
|
||||||
result() {
|
result() {
|
||||||
if (this.$route.query.version && !this.hasValidVersion) {
|
if (this.$route.query.version && !this.hasValidVersion) {
|
||||||
createFlash({
|
createAlert({
|
||||||
message: s__(
|
message: s__(
|
||||||
'DesignManagement|Requested design version does not exist. Showing latest version instead',
|
'DesignManagement|Requested design version does not exist. Showing latest version instead',
|
||||||
),
|
),
|
||||||
|
@ -44,11 +44,11 @@ export default {
|
||||||
this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } });
|
this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } });
|
||||||
}
|
}
|
||||||
if (this.designCollection.copyState === 'ERROR') {
|
if (this.designCollection.copyState === 'ERROR') {
|
||||||
createFlash({
|
createAlert({
|
||||||
message: s__(
|
message: s__(
|
||||||
'DesignManagement|There was an error moving your designs. Please upload your designs below.',
|
'DesignManagement|There was an error moving your designs. Please upload your designs below.',
|
||||||
),
|
),
|
||||||
type: FLASH_TYPES.WARNING,
|
variant: VARIANT_WARNING,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { isNull } from 'lodash';
|
||||||
import Mousetrap from 'mousetrap';
|
import Mousetrap from 'mousetrap';
|
||||||
import { ApolloMutation } from 'vue-apollo';
|
import { ApolloMutation } from 'vue-apollo';
|
||||||
import { keysFor, ISSUE_CLOSE_DESIGN } from '~/behaviors/shortcuts/keybindings';
|
import { keysFor, ISSUE_CLOSE_DESIGN } from '~/behaviors/shortcuts/keybindings';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import { fetchPolicies } from '~/lib/graphql';
|
import { fetchPolicies } from '~/lib/graphql';
|
||||||
import { updateGlobalTodoCount } from '~/vue_shared/components/sidebar/todo_toggle/utils';
|
import { updateGlobalTodoCount } from '~/vue_shared/components/sidebar/todo_toggle/utils';
|
||||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
@ -250,7 +250,7 @@ export default {
|
||||||
onQueryError(message) {
|
onQueryError(message) {
|
||||||
// because we redirect user to /designs (the issue page),
|
// because we redirect user to /designs (the issue page),
|
||||||
// we want to create these flashes on the issue page
|
// we want to create these flashes on the issue page
|
||||||
createFlash({ message });
|
createAlert({ message });
|
||||||
this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME });
|
this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME });
|
||||||
},
|
},
|
||||||
onError(message, e) {
|
onError(message, e) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import { differenceBy } from 'lodash';
|
import { differenceBy } from 'lodash';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import { extractCurrentDiscussion, extractDesign, extractDesigns } from './design_management_utils';
|
import { extractCurrentDiscussion, extractDesign, extractDesigns } from './design_management_utils';
|
||||||
import {
|
import {
|
||||||
ADD_IMAGE_DIFF_NOTE_ERROR,
|
ADD_IMAGE_DIFF_NOTE_ERROR,
|
||||||
|
@ -234,7 +234,7 @@ export const deletePendingTodoFromStore = (store, todoMarkDone, query, queryVari
|
||||||
};
|
};
|
||||||
|
|
||||||
const onError = (data, message) => {
|
const onError = (data, message) => {
|
||||||
createFlash({ message });
|
createAlert({ message });
|
||||||
throw new Error(data.errors);
|
throw new Error(data.errors);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -283,7 +283,7 @@ export const updateStoreAfterUploadDesign = (store, data, query) => {
|
||||||
|
|
||||||
export const updateDesignsOnStoreAfterReorder = (store, data, query) => {
|
export const updateDesignsOnStoreAfterReorder = (store, data, query) => {
|
||||||
if (hasErrors(data)) {
|
if (hasErrors(data)) {
|
||||||
createFlash({ message: data.errors[0] });
|
createAlert({ message: data.errors[0] });
|
||||||
} else {
|
} else {
|
||||||
moveDesignInStore(store, data, query);
|
moveDesignInStore(store, data, query);
|
||||||
}
|
}
|
||||||
|
|
|
@ -412,7 +412,7 @@ export default {
|
||||||
<div
|
<div
|
||||||
v-if="showMultiLineComment"
|
v-if="showMultiLineComment"
|
||||||
data-testid="multiline-comment"
|
data-testid="multiline-comment"
|
||||||
class="gl-mb-5 gl-text-gray-500 gl-border-gray-100 gl-border-b-solid gl-border-b-1 gl-pb-4"
|
class="gl-text-gray-500 gl-border-gray-100 gl-border-b-solid gl-border-b-1 gl-px-5 gl-py-3"
|
||||||
>
|
>
|
||||||
<gl-sprintf :message="__('Comment on lines %{startLine} to %{endLine}')">
|
<gl-sprintf :message="__('Comment on lines %{startLine} to %{endLine}')">
|
||||||
<template #startLine>
|
<template #startLine>
|
||||||
|
|
|
@ -401,7 +401,7 @@ export default {
|
||||||
<div
|
<div
|
||||||
v-for="(variable, index) in variables"
|
v-for="(variable, index) in variables"
|
||||||
:key="variable.uniqueId"
|
:key="variable.uniqueId"
|
||||||
class="gl-mb-3 gl-ml-n3 gl-pb-2"
|
class="gl-mb-3 gl-pb-2"
|
||||||
data-testid="ci-variable-row"
|
data-testid="ci-variable-row"
|
||||||
data-qa-selector="ci_variable_row_container"
|
data-qa-selector="ci_variable_row_container"
|
||||||
>
|
>
|
||||||
|
|
|
@ -401,7 +401,7 @@ export default {
|
||||||
<div
|
<div
|
||||||
v-for="(variable, index) in variables"
|
v-for="(variable, index) in variables"
|
||||||
:key="variable.uniqueId"
|
:key="variable.uniqueId"
|
||||||
class="gl-mb-3 gl-ml-n3 gl-pb-2"
|
class="gl-mb-3 gl-pb-2"
|
||||||
data-testid="ci-variable-row"
|
data-testid="ci-variable-row"
|
||||||
data-qa-selector="ci_variable_row_container"
|
data-qa-selector="ci_variable_row_container"
|
||||||
>
|
>
|
||||||
|
|
|
@ -551,6 +551,11 @@ $system-note-svg-size: 1rem;
|
||||||
.timeline-icon {
|
.timeline-icon {
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-entry-inner .timeline-icon {
|
||||||
|
margin-top: $grid-size;
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -242,11 +242,6 @@ class SearchController < ApplicationController
|
||||||
def search_type
|
def search_type
|
||||||
'basic'
|
'basic'
|
||||||
end
|
end
|
||||||
|
|
||||||
before_action do
|
|
||||||
# Prefer to scope it per project or user e.g.
|
|
||||||
push_frontend_feature_flag(:search_page_vertical_nav, current_user)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
SearchController.prepend_mod_with('SearchController')
|
SearchController.prepend_mod_with('SearchController')
|
||||||
|
|
|
@ -380,42 +380,6 @@ module SearchHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_filter_link_json(scope, label, data, search)
|
|
||||||
search_params = params.merge(search).merge({ scope: scope }).permit(SEARCH_GENERIC_PARAMS)
|
|
||||||
active_scope = @scope == scope
|
|
||||||
|
|
||||||
result = { label: label, scope: scope, data: data, link: search_path(search_params), active: active_scope }
|
|
||||||
result[:count] = @search_results.formatted_count(scope) if active_scope && !@timeout
|
|
||||||
result[:count_link] = search_count_path(search_params) unless active_scope
|
|
||||||
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# search page scope navigation
|
|
||||||
def search_navigation
|
|
||||||
{
|
|
||||||
projects: { label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? },
|
|
||||||
blobs: { label: _("Code"), data: { qa_selector: 'code_tab' }, condition: project_search_tabs?(:blobs) || search_service.show_elasticsearch_tabs? || feature_flag_tab_enabled?(:global_search_code_tab) },
|
|
||||||
epics: { label: _("Epics"), condition: @project.nil? && search_service.show_epics? },
|
|
||||||
issues: { label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) },
|
|
||||||
merge_requests: { label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) },
|
|
||||||
wiki_blobs: { label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? },
|
|
||||||
commits: { label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) },
|
|
||||||
notes: { label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? },
|
|
||||||
milestones: { label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? },
|
|
||||||
users: { label: _("Users"), condition: show_user_search_tab? },
|
|
||||||
snippet_titles: { label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def search_navigation_json
|
|
||||||
result = {}
|
|
||||||
search_navigation.each do |scope, nav|
|
|
||||||
result[scope] = search_filter_link_json(scope.to_s, nav[:label], nav[:data], nav[:search]) if nav[:condition]
|
|
||||||
end
|
|
||||||
result.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def search_filter_input_options(type, placeholder = _('Search or filter results...'))
|
def search_filter_input_options(type, placeholder = _('Search or filter results...'))
|
||||||
opts =
|
opts =
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
def help
|
def help
|
||||||
s_("HarborIntegration|After the Harbor integration is activated, global variables $HARBOR_USERNAME, $HARBOR_HOST, $HARBOR_OCI, $HARBOR_PASSWORD, $HARBOR_URL and $HARBOR_PROJECT will be created for CI/CD use.")
|
s_("HarborIntegration|After the Harbor integration is activated, global variables `$HARBOR_USERNAME`, `$HARBOR_HOST`, `$HARBOR_OCI`, `$HARBOR_PASSWORD`, `$HARBOR_URL` and `$HARBOR_PROJECT` will be created for CI/CD use.")
|
||||||
end
|
end
|
||||||
|
|
||||||
def hostname
|
def hostname
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
= form_for [@project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f|
|
= form_for [@project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f|
|
||||||
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
|
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
|
||||||
.card
|
= render Pajamas::CardComponent.new do |c|
|
||||||
.card-header
|
- c.header do
|
||||||
= _('Protect a tag')
|
= _('Protect a tag')
|
||||||
.card-body
|
- c.body do
|
||||||
= form_errors(@protected_tag)
|
= form_errors(@protected_tag)
|
||||||
.form-group.row
|
.form-group.row
|
||||||
= f.label :name, _('Tag:'), class: 'col-md-2 text-left text-md-right'
|
= f.label :name, _('Tag:'), class: 'col-md-2 text-left text-md-right'
|
||||||
|
@ -19,5 +19,5 @@
|
||||||
.create_access_levels-container
|
.create_access_levels-container
|
||||||
= yield :create_access_levels
|
= yield :create_access_levels
|
||||||
|
|
||||||
.card-footer
|
- c.footer do
|
||||||
= f.submit _('Protect'), class: 'gl-button btn btn-confirm', disabled: true, data: { qa_selector: 'protect_tag_button' }
|
= f.submit _('Protect'), class: 'gl-button btn btn-confirm', disabled: true, data: { qa_selector: 'protect_tag_button' }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%hr
|
%hr
|
||||||
= render Pajamas::CardComponent.new(card_options: { id: 'webhooks-index' }) do |c|
|
= render Pajamas::CardComponent.new(card_options: { id: 'webhooks-index' }, body_options: { class: 'gl-py-0'}) do |c|
|
||||||
- c.header do
|
- c.header do
|
||||||
= hook_class.underscore.humanize.titleize.pluralize
|
= hook_class.underscore.humanize.titleize.pluralize
|
||||||
(#{hooks.size})
|
(#{hooks.size})
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: search_page_vertical_nav
|
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97784
|
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342621
|
|
||||||
milestone: '15.5'
|
|
||||||
type: development
|
|
||||||
group: group::global search
|
|
||||||
default_enabled: false
|
|
|
@ -20,4 +20,5 @@ tier:
|
||||||
performance_indicator_type:
|
performance_indicator_type:
|
||||||
- gmau
|
- gmau
|
||||||
- paid_gmau
|
- paid_gmau
|
||||||
|
- smau
|
||||||
milestone: "<13.9"
|
milestone: "<13.9"
|
||||||
|
|
|
@ -7,9 +7,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
# OpenID Connect OmniAuth provider **(FREE SELF)**
|
# OpenID Connect OmniAuth provider **(FREE SELF)**
|
||||||
|
|
||||||
GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) as an OmniAuth provider.
|
GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html)
|
||||||
|
as an OmniAuth provider.
|
||||||
|
|
||||||
To enable the OpenID Connect OmniAuth provider, you must register your application with an OpenID Connect provider.
|
To enable the OpenID Connect OmniAuth provider, you must register your application
|
||||||
|
with an OpenID Connect provider.
|
||||||
The OpenID Connect provides you with a client's details and secret for you to use.
|
The OpenID Connect provides you with a client's details and secret for you to use.
|
||||||
|
|
||||||
1. On your GitLab server, open the configuration file.
|
1. On your GitLab server, open the configuration file.
|
||||||
|
@ -27,7 +29,7 @@ The OpenID Connect provides you with a client's details and secret for you to us
|
||||||
sudo -u git -H editor config/gitlab.yml
|
sudo -u git -H editor config/gitlab.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
|
1. [Configure initial settings](../../integration/omniauth.md#configure-initial-settings).
|
||||||
|
|
||||||
1. Add the provider configuration.
|
1. Add the provider configuration.
|
||||||
|
|
||||||
|
@ -83,32 +85,49 @@ The OpenID Connect provides you with a client's details and secret for you to us
|
||||||
```
|
```
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
For more information on each configuration option refer to the [OmniAuth OpenID Connect usage documentation](https://github.com/m0n9oose/omniauth_openid_connect#usage)
|
For more information on each configuration option, refer to the:
|
||||||
and the [OpenID Connect Core 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html).
|
|
||||||
|
- [OmniAuth OpenID Connect usage documentation](https://github.com/m0n9oose/omniauth_openid_connect#usage).
|
||||||
|
- [OpenID Connect Core 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html).
|
||||||
|
|
||||||
|
1. For the provider configuration, change the values for the provider to match your
|
||||||
|
OpenID Connect client setup. Use the following as a guide:
|
||||||
|
|
||||||
1. For the configuration above, change the values for the provider to match your OpenID Connect client setup. Use the following as a guide:
|
|
||||||
- `<your_oidc_label>` is the label that appears on the login page.
|
- `<your_oidc_label>` is the label that appears on the login page.
|
||||||
- `<custom_provider_icon>` (optional) is the icon that appears on the login page. Icons for the major social login platforms are built-in into GitLab,
|
- `<custom_provider_icon>` (optional) is the icon that appears on the login page.
|
||||||
but can be overridden by specifying this parameter. Both local paths and absolute URLs are accepted.
|
Icons for the major social login platforms are built into GitLab,
|
||||||
- `<your_oidc_url>` (optional) is the URL that points to the OpenID Connect provider. For example, `https://example.com/auth/realms/your-realm`.
|
but you can override these icons by specifying this parameter. GitLab accepts both
|
||||||
If this value is not provided, the URL is constructed from the `client_options` in the following format: `<client_options.scheme>://<client_options.host>:<client_options.port>`.
|
local paths and absolute URLs.
|
||||||
- If `discovery` is set to `true`, the OpenID Connect provider attempts to auto discover the client options using `<your_oidc_url>/.well-known/openid-configuration`. Defaults to `false`.
|
- `<your_oidc_url>` (optional) is the URL that points to the OpenID Connect
|
||||||
- `client_auth_method` (optional) specifies the method used for authenticating the client with the OpenID Connect provider.
|
provider (for example, `https://example.com/auth/realms/your-realm`).
|
||||||
|
If this value is not provided, the URL is constructed from `client_options`
|
||||||
|
in the following format: `<client_options.scheme>://<client_options.host>:<client_options.port>`.
|
||||||
|
- If `discovery` is set to `true`, the OpenID Connect provider attempts to automatically
|
||||||
|
discover the client options using `<your_oidc_url>/.well-known/openid-configuration`.
|
||||||
|
Defaults to `false`.
|
||||||
|
- `client_auth_method` (optional) specifies the method used for authenticating
|
||||||
|
the client with the OpenID Connect provider.
|
||||||
- Supported values are:
|
- Supported values are:
|
||||||
- `basic` - HTTP Basic Authentication
|
- `basic` - HTTP Basic Authentication.
|
||||||
- `jwt_bearer` - JWT based authentication (private key and client secret signing)
|
- `jwt_bearer` - JWT-based authentication (private key and client secret signing).
|
||||||
- `mtls` - Mutual TLS or X.509 certificate validation
|
- `mtls` - Mutual TLS or X.509 certificate validation.
|
||||||
- Any other value POSTs the client ID and secret in the request body
|
- Any other value posts the client ID and secret in the request body.
|
||||||
- If not specified, defaults to `basic`.
|
- If not specified, this value defaults to `basic`.
|
||||||
- `<uid_field>` (optional) is the field name from the `user_info.raw_attributes` that defines the value for `uid`. For example, `preferred_username`.
|
- `<uid_field>` (optional) is the field name from `user_info.raw_attributes`
|
||||||
If this value is not provided or the field with the configured value is missing from the `user_info.raw_attributes` details, the `uid` uses the `sub` field.
|
that defines the value for `uid` (for example, `preferred_username`).
|
||||||
- `send_scope_to_token_endpoint` is `true` by default. In other words, the `scope` parameter is normally included in requests to the token endpoint.
|
If you do not provide this value, or the field with the configured value is missing
|
||||||
However, if your OpenID Connect provider does not accept the `scope` parameter in such requests, set this to `false`.
|
from the `user_info.raw_attributes` details, `uid` uses the `sub` field.
|
||||||
|
- `send_scope_to_token_endpoint` is `true` by default, so the `scope` parameter
|
||||||
|
is normally included in requests to the token endpoint.
|
||||||
|
However, if your OpenID Connect provider does not accept the `scope` parameter
|
||||||
|
in such requests, set this to `false`.
|
||||||
- `client_options` are the OpenID Connect client-specific options. Specifically:
|
- `client_options` are the OpenID Connect client-specific options. Specifically:
|
||||||
- `identifier` is the client identifier as configured in the OpenID Connect service provider.
|
- `identifier` is the client identifier as configured in the OpenID Connect service provider.
|
||||||
- `secret` is the client secret as configured in the OpenID Connect service provider.
|
- `secret` is the client secret as configured in the OpenID Connect service provider.
|
||||||
- `redirect_uri` is the GitLab URL to redirect the user to after successful login. For example, `http://example.com/users/auth/openid_connect/callback`.
|
- `redirect_uri` is the GitLab URL to redirect the user to after successful login
|
||||||
- `end_session_endpoint` (optional) is the URL to the endpoint that end the session (logout). Can be provided if auto-discovery disabled or unsuccessful.
|
(for example, `http://example.com/users/auth/openid_connect/callback`).
|
||||||
|
- `end_session_endpoint` (optional) is the URL to the endpoint that ends the
|
||||||
|
session. You can provide this URL if auto-discovery is disabled or unsuccessful.
|
||||||
- The following `client_options` are optional unless auto-discovery is disabled or unsuccessful:
|
- The following `client_options` are optional unless auto-discovery is disabled or unsuccessful:
|
||||||
- `authorization_endpoint` is the URL to the endpoint that authorizes the end user.
|
- `authorization_endpoint` is the URL to the endpoint that authorizes the end user.
|
||||||
- `token_endpoint` is the URL to the endpoint that provides Access Token.
|
- `token_endpoint` is the URL to the endpoint that provides Access Token.
|
||||||
|
@ -116,19 +135,22 @@ The OpenID Connect provides you with a client's details and secret for you to us
|
||||||
- `jwks_uri` is the URL to the endpoint where the Token signer publishes its keys.
|
- `jwks_uri` is the URL to the endpoint where the Token signer publishes its keys.
|
||||||
|
|
||||||
1. Save the configuration file.
|
1. Save the configuration file.
|
||||||
1. For changes to take effect, [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) if you installed GitLab with Omnibus, or [restart GitLab](../restart_gitlab.md#installations-from-source) if you installed GitLab from the source.
|
1. For changes to take effect, if you installed GitLab:
|
||||||
|
|
||||||
On the sign in page, there should now be an OpenID Connect icon below the regular sign in form.
|
- With Omnibus, [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||||
Select the icon to begin the authentication process. The OpenID Connect provider asks the user to
|
- From source, [restart GitLab](../restart_gitlab.md#installations-from-source).
|
||||||
sign in and authorize the GitLab application (if confirmation required by the client). If everything goes well, the user
|
|
||||||
is redirected to GitLab and signed in.
|
On the sign in page, you have an OpenID Connect option below the regular sign in form.
|
||||||
|
Select this option to begin the authentication process. The OpenID Connect provider
|
||||||
|
asks you to sign in and authorize the GitLab application if confirmation is required
|
||||||
|
by the client. You are redirected to GitLab and signed in.
|
||||||
|
|
||||||
## Example configurations
|
## Example configurations
|
||||||
|
|
||||||
The following configurations illustrate how to set up OpenID with
|
The following configurations illustrate how to set up OpenID with
|
||||||
different providers with Omnibus GitLab.
|
different providers with Omnibus GitLab.
|
||||||
|
|
||||||
### Google
|
### Configure Google
|
||||||
|
|
||||||
See the [Google documentation](https://developers.google.com/identity/protocols/oauth2/openid-connect)
|
See the [Google documentation](https://developers.google.com/identity/protocols/oauth2/openid-connect)
|
||||||
for more details:
|
for more details:
|
||||||
|
@ -156,13 +178,13 @@ gitlab_rails['omniauth_providers'] = [
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Microsoft Azure
|
### Configure Microsoft Azure
|
||||||
|
|
||||||
The OpenID Connect (OIDC) protocol for Microsoft Azure uses the [Microsoft identity platform (v2) endpoints](https://learn.microsoft.com/en-us/azure/active-directory/azuread-dev/azure-ad-endpoint-comparison).
|
The OpenID Connect (OIDC) protocol for Microsoft Azure uses the [Microsoft identity platform (v2) endpoints](https://learn.microsoft.com/en-us/azure/active-directory/azuread-dev/azure-ad-endpoint-comparison).
|
||||||
To get started, sign in to the [Azure Portal](https://portal.azure.com). For your app, you need the
|
To get started, sign in to the [Azure Portal](https://portal.azure.com). For your app,
|
||||||
following information:
|
you need the following information:
|
||||||
|
|
||||||
- A tenant ID. You may already have one. For more information, review the
|
- A tenant ID. You may already have one. For more information, see the
|
||||||
[Microsoft Azure Tenant](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) documentation.
|
[Microsoft Azure Tenant](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) documentation.
|
||||||
- A client ID and a client secret. Follow the instructions in the
|
- A client ID and a client secret. Follow the instructions in the
|
||||||
[Microsoft Quickstart Register an Application](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) documentation
|
[Microsoft Quickstart Register an Application](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) documentation
|
||||||
|
@ -195,10 +217,10 @@ gitlab_rails['omniauth_providers'] = [
|
||||||
|
|
||||||
Microsoft has documented how its platform works with [the OIDC protocol](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc).
|
Microsoft has documented how its platform works with [the OIDC protocol](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc).
|
||||||
|
|
||||||
### Microsoft Azure Active Directory B2C
|
### Configure Microsoft Azure Active Directory B2C
|
||||||
|
|
||||||
While GitLab works with [Azure Active Directory B2C](https://learn.microsoft.com/en-us/azure/active-directory-b2c/overview), it requires special
|
GitLab requires special
|
||||||
configuration to work. To get started, sign in to the [Azure Portal](https://portal.azure.com).
|
configuration to work with [Azure Active Directory B2C](https://learn.microsoft.com/en-us/azure/active-directory-b2c/overview). To get started, sign in to the [Azure Portal](https://portal.azure.com).
|
||||||
For your app, you need the following information from Azure:
|
For your app, you need the following information from Azure:
|
||||||
|
|
||||||
- A tenant ID. You may already have one. For more information, review the
|
- A tenant ID. You may already have one. For more information, review the
|
||||||
|
@ -208,16 +230,17 @@ For your app, you need the following information from Azure:
|
||||||
client ID and client secret for your app.
|
client ID and client secret for your app.
|
||||||
- The user flow or policy name. Follow the instructions in the [Microsoft tutorial](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-user-flow).
|
- The user flow or policy name. Follow the instructions in the [Microsoft tutorial](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-user-flow).
|
||||||
|
|
||||||
If your GitLab domain is `gitlab.example.com`, ensure the app has the following `Redirect URI`:
|
Configure the app:
|
||||||
|
|
||||||
`https://gitlab.example.com/users/auth/openid_connect/callback`
|
1. Set the app `Redirect URI`. For example, If your GitLab domain is `gitlab.example.com`,
|
||||||
|
set the app `Redirect URI` to `https://gitlab.example.com/users/auth/openid_connect/callback`.
|
||||||
|
|
||||||
In addition, ensure that [ID tokens are enabled](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga#enable-id-token-implicit-grant).
|
1. [Enable the ID tokens](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga#enable-id-token-implicit-grant).
|
||||||
|
|
||||||
Add the following API permissions to the app:
|
1. Add the following API permissions to the app:
|
||||||
|
|
||||||
- `openid`
|
- `openid`
|
||||||
- `offline_access`
|
- `offline_access`
|
||||||
|
|
||||||
#### Configure custom policies
|
#### Configure custom policies
|
||||||
|
|
||||||
|
@ -226,16 +249,17 @@ Azure B2C [offers two ways of defining the business logic for logging in a user]
|
||||||
- [User flows](https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#user-flows)
|
- [User flows](https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#user-flows)
|
||||||
- [Custom policies](https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#custom-policies)
|
- [Custom policies](https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#custom-policies)
|
||||||
|
|
||||||
While cumbersome to configure, custom policies are required because
|
Custom policies are required because standard Azure B2C user flows
|
||||||
standard Azure B2C user flows [do not send the OpenID `email` claim](https://github.com/MicrosoftDocs/azure-docs/issues/16566). In
|
[do not send the OpenID `email` claim](https://github.com/MicrosoftDocs/azure-docs/issues/16566).
|
||||||
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user` parameters](../../integration/omniauth.md#configure-initial-settings).
|
Therefore, the standard user flows do not work with the
|
||||||
|
[`allow_single_sign_on` or `auto_link_user` parameters](../../integration/omniauth.md#configure-initial-settings).
|
||||||
With a standard Azure B2C policy, GitLab cannot create a new account or
|
With a standard Azure B2C policy, GitLab cannot create a new account or
|
||||||
link to an existing one with an email address.
|
link to an existing account with an email address.
|
||||||
|
|
||||||
Carefully follow the instructions for [creating a custom policy](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy).
|
First, [create a custom policy](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy).
|
||||||
|
|
||||||
The Microsoft instructions use `SocialAndLocalAccounts` in the [custom policy starter pack](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#custom-policy-starter-pack),
|
The Microsoft instructions use `SocialAndLocalAccounts` in the [custom policy starter pack](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#custom-policy-starter-pack),
|
||||||
but `LocalAccounts` works for authenticating against local, Active Directory accounts. Before you follow the instructions to [upload the polices](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#upload-the-policies), do the following:
|
but `LocalAccounts` authenticates against local Active Directory accounts. Before you [upload the polices](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#upload-the-policies), do the following:
|
||||||
|
|
||||||
1. To export the `email` claim, modify the `SignUpOrSignin.xml`. Replace the following line:
|
1. To export the `email` claim, modify the `SignUpOrSignin.xml`. Replace the following line:
|
||||||
|
|
||||||
|
@ -249,7 +273,7 @@ but `LocalAccounts` works for authenticating against local, Active Directory acc
|
||||||
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
|
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
|
||||||
```
|
```
|
||||||
|
|
||||||
1. For OIDC discovery to work with B2C, the policy must be configured with an issuer compatible with the
|
1. For OIDC discovery to work with B2C, configure the policy with an issuer compatible with the
|
||||||
[OIDC specification](https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.4.3).
|
[OIDC specification](https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.4.3).
|
||||||
See the [token compatibility settings](https://learn.microsoft.com/en-us/azure/active-directory-b2c/configure-tokens?pivots=b2c-custom-policy#token-compatibility-settings).
|
See the [token compatibility settings](https://learn.microsoft.com/en-us/azure/active-directory-b2c/configure-tokens?pivots=b2c-custom-policy#token-compatibility-settings).
|
||||||
In `TrustFrameworkBase.xml` under `JwtIssuer`, set `IssuanceClaimPattern` to `AuthorityWithTfp`:
|
In `TrustFrameworkBase.xml` under `JwtIssuer`, set `IssuanceClaimPattern` to `AuthorityWithTfp`:
|
||||||
|
@ -267,10 +291,10 @@ but `LocalAccounts` works for authenticating against local, Active Directory acc
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Now [upload the policy](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#upload-the-policies). Overwrite
|
1. [Upload the policy](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#upload-the-policies). Overwrite
|
||||||
the existing files if you are updating an existing policy.
|
the existing files if you are updating an existing policy.
|
||||||
|
|
||||||
1. Determine the issuer URL using the sign-in policy. The issuer URL is in the form:
|
1. To determine the issuer URL, use the sign-in policy. The issuer URL is in the form:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
https://<YOUR-DOMAIN>/tfp/<YOUR-TENANT-ID>/<YOUR-SIGN-IN-POLICY-NAME>/v2.0/
|
https://<YOUR-DOMAIN>/tfp/<YOUR-TENANT-ID>/<YOUR-SIGN-IN-POLICY-NAME>/v2.0/
|
||||||
|
@ -279,10 +303,10 @@ but `LocalAccounts` works for authenticating against local, Active Directory acc
|
||||||
The policy name is lowercase in the URL. For example, `B2C_1A_signup_signin`
|
The policy name is lowercase in the URL. For example, `B2C_1A_signup_signin`
|
||||||
policy appears as `b2c_1a_signup_sigin`.
|
policy appears as `b2c_1a_signup_sigin`.
|
||||||
|
|
||||||
The trailing forward slash is required.
|
Ensure you include the trailing forward slash.
|
||||||
|
|
||||||
1. Verify the operation of the OIDC discovery URL and issuer URL, append `.well-known/openid-configuration`
|
1. Verify the operation of the OIDC discovery URL and issuer URL and append
|
||||||
to the issuer URL:
|
`.well-known/openid-configuration` to the issuer URL:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
https://<YOUR-DOMAIN>/tfp/<YOUR-TENANT-ID>/<YOUR-SIGN-IN-POLICY-NAME>/v2.0/.well-known/openid-configuration
|
https://<YOUR-DOMAIN>/tfp/<YOUR-TENANT-ID>/<YOUR-SIGN-IN-POLICY-NAME>/v2.0/.well-known/openid-configuration
|
||||||
|
@ -327,32 +351,34 @@ The trailing forward slash is required.
|
||||||
- Ensure all occurrences of `yourtenant.onmicrosoft.com`, `ProxyIdentityExperienceFrameworkAppId`, and `IdentityExperienceFrameworkAppId` match your B2C tenant hostname and
|
- Ensure all occurrences of `yourtenant.onmicrosoft.com`, `ProxyIdentityExperienceFrameworkAppId`, and `IdentityExperienceFrameworkAppId` match your B2C tenant hostname and
|
||||||
the respective client IDs in the XML policy files.
|
the respective client IDs in the XML policy files.
|
||||||
- Add `https://jwt.ms` as a redirect URI to the app, and use the [custom policy tester](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#test-the-custom-policy).
|
- Add `https://jwt.ms` as a redirect URI to the app, and use the [custom policy tester](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#test-the-custom-policy).
|
||||||
Make sure the payload includes `email` that matches the user's email access.
|
Ensure the payload includes `email` that matches the user's email access.
|
||||||
- After you enable the custom policy, users might see "Invalid username or password" after they try to sign in. This might be a configuration
|
- After you enable the custom policy, users might see `Invalid username or password`
|
||||||
issue with the `IdentityExperienceFramework` app. See [this Microsoft comment](https://learn.microsoft.com/en-us/answers/questions/50355/unable-to-sign-on-using-custom-policy.html?childToView=122370#comment-122370)
|
after they try to sign in. This might be a configuration issue with the `IdentityExperienceFramework`
|
||||||
that suggests checking that the app manifest contains these settings:
|
app. See [this Microsoft comment](https://learn.microsoft.com/en-us/answers/questions/50355/unable-to-sign-on-using-custom-policy.html?childToView=122370#comment-122370) that suggests you check that the app manifest
|
||||||
|
contains these settings:
|
||||||
|
|
||||||
- `"accessTokenAcceptedVersion": null`
|
- `"accessTokenAcceptedVersion": null`
|
||||||
- `"signInAudience": "AzureADMyOrg"`
|
- `"signInAudience": "AzureADMyOrg"`
|
||||||
|
|
||||||
This configuration corresponds with the `Supported account types` setting used when
|
This configuration corresponds with the `Supported account types` setting used when
|
||||||
creating the `IdentityExperienceFramework` app.
|
creating the `IdentityExperienceFramework` app.
|
||||||
|
|
||||||
### Keycloak
|
### Configure Keycloak
|
||||||
|
|
||||||
GitLab works with OpenID providers that use HTTPS. Although a Keycloak
|
GitLab works with OpenID providers that use HTTPS. Although you can set up a
|
||||||
server can be set up using HTTP, GitLab can only communicate
|
Keycloak server that uses HTTP, GitLab can only communicate with a Keycloak server
|
||||||
with a Keycloak server that uses HTTPS.
|
that uses HTTPS.
|
||||||
|
|
||||||
We highly recommend configuring Keycloak to use public key encryption algorithms (for example,
|
Configure Keycloak to use public key encryption algorithms (for example,
|
||||||
RSA256, RSA512, and so on) instead of symmetric key encryption algorithms (for example, HS256 or HS358) to
|
RSA256 or RSA512) instead of symmetric key encryption algorithms (for example,
|
||||||
sign tokens. Public key encryption algorithms are:
|
HS256 or HS358) to sign tokens. Public key encryption algorithms are:
|
||||||
|
|
||||||
- Easier to configure.
|
- Easier to configure.
|
||||||
- More secure because leaking the private key has severe security consequences.
|
- More secure because leaking the private key has severe security consequences.
|
||||||
|
|
||||||
The signature algorithm can be configured in the Keycloak administration console under
|
1. Open the Keycloak administration console.
|
||||||
**Realm Settings > Tokens > Default Signature Algorithm**.
|
1. Select **Realm Settings > Tokens > Default Signature Algorithm**.
|
||||||
|
1. Configure the signature algorithm.
|
||||||
|
|
||||||
Example Omnibus configuration block:
|
Example Omnibus configuration block:
|
||||||
|
|
||||||
|
@ -384,37 +410,39 @@ gitlab_rails['omniauth_providers'] = [
|
||||||
> Introduced in GitLab 14.2.
|
> Introduced in GitLab 14.2.
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
The instructions below are included for completeness, but symmetric key
|
The following instructions are included for completeness, but only use symmetric key
|
||||||
encryption should only be used when absolutely necessary.
|
encryption if absolutely necessary.
|
||||||
|
|
||||||
To use symmetric key encryption:
|
To use symmetric key encryption:
|
||||||
|
|
||||||
1. Extract the secret key from the Keycloak database. Keycloak doesn't expose this value in the Web
|
1. Extract the secret key from the Keycloak database. Keycloak does not expose this
|
||||||
interface. The client secret seen in the Web interface is the OAuth2 client secret, which is
|
value in the web interface. The client secret seen in the web interface is the
|
||||||
different from the secret used to sign JSON Web Tokens.
|
OAuth 2.0 client secret, which is different from the secret used to sign JSON Web Tokens.
|
||||||
|
|
||||||
For example, if you're using PostgreSQL as the backend database for Keycloak, log in to the
|
For example, if you use PostgreSQL as the backend database for Keycloak:
|
||||||
database console and extract the key via this SQL query:
|
|
||||||
|
|
||||||
```sql
|
- Sign into the database console.
|
||||||
$ psql -U keycloak
|
- Run the following SQL query to extract the key:
|
||||||
psql (13.3 (Debian 13.3-1.pgdg100+1))
|
|
||||||
Type "help" for help.
|
|
||||||
|
|
||||||
keycloak=# SELECT c.name, value FROM component_config CC INNER JOIN component C ON(CC.component_id = C.id) WHERE C.realm_id = 'master' and provider_id = 'hmac-generated' AND CC.name = 'secret';
|
```sql
|
||||||
-[ RECORD 1 ]---------------------------------------------------------------------------------
|
$ psql -U keycloak
|
||||||
name | hmac-generated
|
psql (13.3 (Debian 13.3-1.pgdg100+1))
|
||||||
value | lo6cqjD6Ika8pk7qc3fpFx9ysrhf7E62-sqGc8drp3XW-wr93zru8PFsQokHZZuJJbaUXvmiOftCZM3C4KW3-g
|
Type "help" for help.
|
||||||
-[ RECORD 2 ]---------------------------------------------------------------------------------
|
|
||||||
name | fallback-HS384
|
|
||||||
value | UfVqmIs--U61UYsRH-NYBH3_mlluLONpg_zN7CXEwkJcO9xdRNlzZfmfDLPtf2xSTMvqu08R2VhLr-8G-oZ47A
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example, there are two private keys: one for HS256 (`hmac-generated`), and another for
|
keycloak=# SELECT c.name, value FROM component_config CC INNER JOIN component C ON(CC.component_id = C.id) WHERE C.realm_id = 'master' and provider_id = 'hmac-generated' AND CC.name = 'secret';
|
||||||
HS384 (`fallback-HS384`). We use the first `value` to configure GitLab.
|
-[ RECORD 1 ]---------------------------------------------------------------------------------
|
||||||
|
name | hmac-generated
|
||||||
|
value | lo6cqjD6Ika8pk7qc3fpFx9ysrhf7E62-sqGc8drp3XW-wr93zru8PFsQokHZZuJJbaUXvmiOftCZM3C4KW3-g
|
||||||
|
-[ RECORD 2 ]---------------------------------------------------------------------------------
|
||||||
|
name | fallback-HS384
|
||||||
|
value | UfVqmIs--U61UYsRH-NYBH3_mlluLONpg_zN7CXEwkJcO9xdRNlzZfmfDLPtf2xSTMvqu08R2VhLr-8G-oZ47A
|
||||||
|
```
|
||||||
|
|
||||||
1. Convert `value` to standard base64. As [discussed in the post](https://keycloak.discourse.group/t/invalid-signature-with-hs256-token/3228/9),
|
In this example, there are two private keys: one for HS256 (`hmac-generated`)
|
||||||
`value` is encoded in ["Base 64 Encoding with URL and Filename Safe Alphabet" in RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-5).
|
and another for HS384 (`fallback-HS384`). We use the first `value` to configure GitLab.
|
||||||
|
|
||||||
|
1. Convert `value` to standard base64. As discussed in the [**Invalid signature with HS256 token** post](https://keycloak.discourse.group/t/invalid-signature-with-hs256-token/3228/9),
|
||||||
|
`value` is encoded in the [**Base 64 Encoding with URL and Filename Safe Alphabet** section](https://datatracker.ietf.org/doc/html/rfc4648#section-5) of RFC 4648.
|
||||||
This must be converted to [standard base64 as defined in RFC 2045](https://datatracker.ietf.org/doc/html/rfc2045).
|
This must be converted to [standard base64 as defined in RFC 2045](https://datatracker.ietf.org/doc/html/rfc2045).
|
||||||
The following Ruby script does this:
|
The following Ruby script does this:
|
||||||
|
|
||||||
|
@ -457,17 +485,19 @@ To use symmetric key encryption:
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
If after reconfiguring, you see the error `JSON::JWS::VerificationFailed` error message, this means
|
If you see a `JSON::JWS::VerificationFailed` error,
|
||||||
the incorrect secret was specified.
|
you have specified the wrong secret.
|
||||||
|
|
||||||
### Casdoor
|
### Casdoor
|
||||||
|
|
||||||
GitLab works with OpenID providers that use HTTPS. To connect to GitLab using OpenID with Casdoor, use HTTPS instead of HTTP.
|
GitLab works with OpenID providers that use HTTPS. Use HTTPS to connect to GitLab
|
||||||
|
through OpenID with Casdoor.
|
||||||
|
|
||||||
For your app, complete the following steps on Casdoor:
|
For your app, complete the following steps on Casdoor:
|
||||||
|
|
||||||
1. Get a client ID and a client secret.
|
1. Get a client ID and a client secret.
|
||||||
1. Add your GitLab redirect URL. For example, if your GitLab domain is `gitlab.example.com`, ensure the Casdoor app has the following
|
1. Add your GitLab redirect URL. For example, if your GitLab domain is `gitlab.example.com`,
|
||||||
|
ensure the Casdoor app has the following
|
||||||
`Redirect URI`: `https://gitlab.example.com/users/auth/openid_connect/callback`.
|
`Redirect URI`: `https://gitlab.example.com/users/auth/openid_connect/callback`.
|
||||||
|
|
||||||
See the [Casdoor documentation](https://casdoor.org/docs/integration/gitlab) for more details.
|
See the [Casdoor documentation](https://casdoor.org/docs/integration/gitlab) for more details.
|
||||||
|
@ -519,23 +549,21 @@ Example installations from source configuration (file path: `config/gitlab.yml`)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## General troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If you're having trouble, here are some tips:
|
1. Ensure `discovery` is set to `true`. If you set it to `false`, you must
|
||||||
|
specify all the URLs and keys required to make OpenID work.
|
||||||
1. Ensure `discovery` is set to `true`. Setting it to `false` requires
|
|
||||||
specifying all the URLs and keys required to make OpenID work.
|
|
||||||
|
|
||||||
1. Check your system clock to ensure the time is synchronized properly.
|
1. Check your system clock to ensure the time is synchronized properly.
|
||||||
|
|
||||||
1. As mentioned in [the documentation](https://github.com/m0n9oose/omniauth_openid_connect),
|
1. As mentioned in [the OmniAuth OpenID Connect documentation](https://github.com/m0n9oose/omniauth_openid_connect),
|
||||||
make sure `issuer` corresponds to the base URL of the Discovery URL. For
|
make sure `issuer` corresponds to the base URL of the Discovery URL. For
|
||||||
example, `https://accounts.google.com` is used for the URL
|
example, `https://accounts.google.com` is used for the URL
|
||||||
`https://accounts.google.com/.well-known/openid-configuration`.
|
`https://accounts.google.com/.well-known/openid-configuration`.
|
||||||
|
|
||||||
1. The OpenID Connect client uses HTTP Basic Authentication to send the
|
1. The OpenID Connect client uses HTTP Basic Authentication to send the
|
||||||
OAuth2 access token if `client_auth_method` is not defined or if set to `basic`.
|
OAuth 2.0 access token if `client_auth_method` is not defined or if set to `basic`.
|
||||||
If you are seeing 401 errors upon retrieving the `userinfo` endpoint, you may
|
If you see 401 errors when retrieving the `userinfo` endpoint, check
|
||||||
want to check your OpenID Web server configuration. For example, for
|
your OpenID web server configuration. For example, for
|
||||||
[`oauth2-server-php`](https://github.com/bshaffer/oauth2-server-php), you may need to
|
[`oauth2-server-php`](https://github.com/bshaffer/oauth2-server-php), you may have to
|
||||||
[add a configuration parameter to Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778).
|
[add a configuration parameter to Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778).
|
||||||
|
|
|
@ -23021,6 +23021,7 @@ Field that are available while modifying the custom mapping attributes for an HT
|
||||||
| <a id="boardissueinputconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter by confidentiality. |
|
| <a id="boardissueinputconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter by confidentiality. |
|
||||||
| <a id="boardissueinputepicid"></a>`epicId` | [`EpicID`](#epicid) | Filter by epic ID. Incompatible with epicWildcardId. |
|
| <a id="boardissueinputepicid"></a>`epicId` | [`EpicID`](#epicid) | Filter by epic ID. Incompatible with epicWildcardId. |
|
||||||
| <a id="boardissueinputepicwildcardid"></a>`epicWildcardId` | [`EpicWildcardId`](#epicwildcardid) | Filter by epic ID wildcard. Incompatible with epicId. |
|
| <a id="boardissueinputepicwildcardid"></a>`epicWildcardId` | [`EpicWildcardId`](#epicwildcardid) | Filter by epic ID wildcard. Incompatible with epicId. |
|
||||||
|
| <a id="boardissueinputhealthstatusfilter"></a>`healthStatusFilter` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the issue, "none" and "any" values are supported. |
|
||||||
| <a id="boardissueinputiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example `["1", "2"]`. |
|
| <a id="boardissueinputiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example `["1", "2"]`. |
|
||||||
| <a id="boardissueinputiterationcadenceid"></a>`iterationCadenceId` | [`[IterationsCadenceID!]`](#iterationscadenceid) | Filter by a list of iteration cadence IDs. |
|
| <a id="boardissueinputiterationcadenceid"></a>`iterationCadenceId` | [`[IterationsCadenceID!]`](#iterationscadenceid) | Filter by a list of iteration cadence IDs. |
|
||||||
| <a id="boardissueinputiterationid"></a>`iterationId` | [`[IterationID!]`](#iterationid) | Filter by a list of iteration IDs. Incompatible with iterationWildcardId. |
|
| <a id="boardissueinputiterationid"></a>`iterationId` | [`[IterationID!]`](#iterationid) | Filter by a list of iteration IDs. Incompatible with iterationWildcardId. |
|
||||||
|
|
|
@ -13,7 +13,7 @@ or the provider that uses the v2.0 endpoint.
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
For new projects, Microsoft suggests you use the
|
For new projects, Microsoft suggests you use the
|
||||||
[OpenID Connect protocol](../administration/auth/oidc.md#microsoft-azure),
|
[OpenID Connect protocol](../administration/auth/oidc.md#configure-microsoft-azure),
|
||||||
which uses the Microsoft identity platform (v2.0) endpoint.
|
which uses the Microsoft identity platform (v2.0) endpoint.
|
||||||
|
|
||||||
## Register an Azure application
|
## Register an Azure application
|
||||||
|
|
|
@ -183,7 +183,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_query(str:, type:, include_collaborations: true, include_orgs: true)
|
def search_query(str:, type:, include_collaborations: true, include_orgs: true)
|
||||||
query = "#{str} in:#{type} is:public,private user:#{octokit.user.login}"
|
query = "#{str} in:#{type} is:public,private user:#{octokit.user.to_h[:login]}"
|
||||||
|
|
||||||
query = [query, collaborations_subquery].join(' ') if include_collaborations
|
query = [query, collaborations_subquery].join(' ') if include_collaborations
|
||||||
query = [query, organizations_subquery].join(' ') if include_orgs
|
query = [query, organizations_subquery].join(' ') if include_orgs
|
||||||
|
@ -274,13 +274,13 @@ module Gitlab
|
||||||
|
|
||||||
def collaborations_subquery
|
def collaborations_subquery
|
||||||
each_object(:repos, nil, { affiliation: 'collaborator' })
|
each_object(:repos, nil, { affiliation: 'collaborator' })
|
||||||
.map { |repo| "repo:#{repo.full_name}" }
|
.map { |repo| "repo:#{repo[:full_name]}" }
|
||||||
.join(' ')
|
.join(' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def organizations_subquery
|
def organizations_subquery
|
||||||
each_object(:organizations)
|
each_object(:organizations)
|
||||||
.map { |org| "org:#{org.login}" }
|
.map { |org| "org:#{org[:login]}" }
|
||||||
.join(' ')
|
.join(' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ module Gitlab
|
||||||
CATEGORIES_FOR_TOTALS = %w[
|
CATEGORIES_FOR_TOTALS = %w[
|
||||||
analytics
|
analytics
|
||||||
compliance
|
compliance
|
||||||
epics_usage
|
|
||||||
error_tracking
|
error_tracking
|
||||||
ide_edit
|
ide_edit
|
||||||
incident_management
|
incident_management
|
||||||
|
|
|
@ -1,227 +0,0 @@
|
||||||
# Epic events
|
|
||||||
#
|
|
||||||
# We are using the same slot of issue events 'project_management' for
|
|
||||||
# epic events to allow data aggregation.
|
|
||||||
# More information in: https://gitlab.com/gitlab-org/gitlab/-/issues/322405
|
|
||||||
- name: g_project_management_epic_created
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
# content change events
|
|
||||||
|
|
||||||
- name: project_management_users_unchecking_epic_task
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: project_management_users_checking_epic_task
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_updating_epic_titles
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_updating_epic_descriptions
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
# epic notes
|
|
||||||
|
|
||||||
- name: g_project_management_users_creating_epic_notes
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_updating_epic_notes
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_destroying_epic_notes
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
# emoji
|
|
||||||
|
|
||||||
- name: g_project_management_users_awarding_epic_emoji
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_removing_epic_emoji
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
# start date events
|
|
||||||
|
|
||||||
- name: g_project_management_users_setting_epic_start_date_as_fixed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_updating_fixed_epic_start_date
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_setting_epic_start_date_as_inherited
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
# due date events
|
|
||||||
|
|
||||||
- name: g_project_management_users_setting_epic_due_date_as_fixed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_updating_fixed_epic_due_date
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_setting_epic_due_date_as_inherited
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
# relationships
|
|
||||||
|
|
||||||
- name: g_project_management_epic_issue_added
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_issue_removed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_issue_moved_from_project
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_updating_epic_parent
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_closed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_reopened
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: 'g_project_management_issue_promoted_to_epic'
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_setting_epic_confidential
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_setting_epic_visible
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_users_changing_labels
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_destroyed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_cross_referenced
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_users_epic_issue_added_from_epic
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_related_added
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_related_removed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_blocking_added
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_blocking_removed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_blocked_added
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
||||||
- name: g_project_management_epic_blocked_removed
|
|
||||||
category: epics_usage
|
|
||||||
redis_slot: project_management
|
|
||||||
aggregation: daily
|
|
||||||
feature_flag: track_epics_activity
|
|
||||||
|
|
|
@ -3458,9 +3458,21 @@ msgstr ""
|
||||||
msgid "AdvancedSearch|Elasticsearch version not compatible"
|
msgid "AdvancedSearch|Elasticsearch version not compatible"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AdvancedSearch|Introduced in GitLab 13.1, before using %{reindexing_link_start}zero-downtime reindexing%{link_end} and %{migrations_link_start}Advanced Search migrations%{link_end}, you need to %{recreate_link_start}recreate your index%{link_end}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AdvancedSearch|Pause indexing and upgrade Elasticsearch to a supported version."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AdvancedSearch|Reindex recommended"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "AdvancedSearch|Reindex required"
|
msgid "AdvancedSearch|Reindex required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AdvancedSearch|You are using outdated code search mappings. To improve code search quality, we recommend you use %{reindexing_link_start}zero-downtime reindexing%{link_end} or %{recreate_link_start}re-create your index%{link_end}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "After a successful password update you will be redirected to login screen."
|
msgid "After a successful password update you will be redirected to login screen."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -19470,7 +19482,7 @@ msgstr ""
|
||||||
msgid "Harbor Registry"
|
msgid "Harbor Registry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "HarborIntegration|After the Harbor integration is activated, global variables $HARBOR_USERNAME, $HARBOR_HOST, $HARBOR_OCI, $HARBOR_PASSWORD, $HARBOR_URL and $HARBOR_PROJECT will be created for CI/CD use."
|
msgid "HarborIntegration|After the Harbor integration is activated, global variables `$HARBOR_USERNAME`, `$HARBOR_HOST`, `$HARBOR_OCI`, `$HARBOR_PASSWORD`, `$HARBOR_URL` and `$HARBOR_PROJECT` will be created for CI/CD use."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "HarborIntegration|Base URL of the Harbor instance."
|
msgid "HarborIntegration|Base URL of the Harbor instance."
|
||||||
|
@ -21838,9 +21850,6 @@ msgstr ""
|
||||||
msgid "Interval Pattern"
|
msgid "Interval Pattern"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Introduced in GitLab 13.1, before using %{reindexing_link_start}zero-downtime reindexing%{link_end} and %{migrations_link_start}Advanced Search migrations%{link_end}, you need to %{recreate_link_start}recreate your index%{link_end}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Introducing Your DevOps Reports"
|
msgid "Introducing Your DevOps Reports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -29025,9 +29034,6 @@ msgstr ""
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pause indexing and upgrade Elasticsearch to a supported version."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Pause time (ms)"
|
msgid "Pause time (ms)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -218,7 +218,7 @@ RSpec.describe Import::GithubController do
|
||||||
|
|
||||||
it 'makes request to github search api' do
|
it 'makes request to github search api' do
|
||||||
expect_next_instance_of(Octokit::Client) do |client|
|
expect_next_instance_of(Octokit::Client) do |client|
|
||||||
expect(client).to receive(:user).and_return(double(login: user_login))
|
expect(client).to receive(:user).and_return({ login: user_login })
|
||||||
expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum })
|
expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ RSpec.describe Import::GithubController do
|
||||||
context 'when no page is specified' do
|
context 'when no page is specified' do
|
||||||
it 'requests first page' do
|
it 'requests first page' do
|
||||||
expect_next_instance_of(Octokit::Client) do |client|
|
expect_next_instance_of(Octokit::Client) do |client|
|
||||||
expect(client).to receive(:user).and_return(double(login: user_login))
|
expect(client).to receive(:user).and_return({ login: user_login })
|
||||||
expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum })
|
expect(client).to receive(:search_repositories).with(search_query, { page: 1, per_page: 25 }).and_return({ items: [].to_enum })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ RSpec.describe Import::GithubController do
|
||||||
context 'when page is specified' do
|
context 'when page is specified' do
|
||||||
it 'requests repos with specified page' do
|
it 'requests repos with specified page' do
|
||||||
expect_next_instance_of(Octokit::Client) do |client|
|
expect_next_instance_of(Octokit::Client) do |client|
|
||||||
expect(client).to receive(:user).and_return(double(login: user_login))
|
expect(client).to receive(:user).and_return({ login: user_login })
|
||||||
expect(client).to receive(:search_repositories).with(search_query, { page: 2, per_page: 25 }).and_return({ items: [].to_enum })
|
expect(client).to receive(:search_repositories).with(search_query, { page: 2, per_page: 25 }).and_return({ items: [].to_enum })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
DESIGN_SNOWPLOW_EVENT_TYPES,
|
DESIGN_SNOWPLOW_EVENT_TYPES,
|
||||||
DESIGN_SERVICE_PING_EVENT_TYPES,
|
DESIGN_SERVICE_PING_EVENT_TYPES,
|
||||||
} from '~/design_management/utils/tracking';
|
} from '~/design_management/utils/tracking';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import mockAllVersions from '../../mock_data/all_versions';
|
import mockAllVersions from '../../mock_data/all_versions';
|
||||||
import design from '../../mock_data/design';
|
import design from '../../mock_data/design';
|
||||||
import mockResponseWithDesigns from '../../mock_data/designs';
|
import mockResponseWithDesigns from '../../mock_data/designs';
|
||||||
|
@ -301,8 +301,8 @@ describe('Design management design index page', () => {
|
||||||
|
|
||||||
wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
|
wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
expect(createAlert).toHaveBeenCalledTimes(1);
|
||||||
expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_NOT_FOUND_ERROR });
|
expect(createAlert).toHaveBeenCalledWith({ message: DESIGN_NOT_FOUND_ERROR });
|
||||||
expect(router.push).toHaveBeenCalledTimes(1);
|
expect(router.push).toHaveBeenCalledTimes(1);
|
||||||
expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
|
expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
|
||||||
});
|
});
|
||||||
|
@ -323,8 +323,8 @@ describe('Design management design index page', () => {
|
||||||
|
|
||||||
wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
|
wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
expect(createAlert).toHaveBeenCalledTimes(1);
|
||||||
expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_VERSION_NOT_EXIST_ERROR });
|
expect(createAlert).toHaveBeenCalledWith({ message: DESIGN_VERSION_NOT_EXIST_ERROR });
|
||||||
expect(router.push).toHaveBeenCalledTimes(1);
|
expect(router.push).toHaveBeenCalledTimes(1);
|
||||||
expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
|
expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
DESIGN_TRACKING_PAGE_NAME,
|
DESIGN_TRACKING_PAGE_NAME,
|
||||||
DESIGN_SNOWPLOW_EVENT_TYPES,
|
DESIGN_SNOWPLOW_EVENT_TYPES,
|
||||||
} from '~/design_management/utils/tracking';
|
} from '~/design_management/utils/tracking';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
|
import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
|
||||||
import {
|
import {
|
||||||
designListQueryResponse,
|
designListQueryResponse,
|
||||||
|
@ -808,7 +808,7 @@ describe('Design management index page', () => {
|
||||||
await moveDesigns(wrapper);
|
await moveDesigns(wrapper);
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
|
expect(createAlert).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays alert if mutation had a non-recoverable error', async () => {
|
it('displays alert if mutation had a non-recoverable error', async () => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
ADD_IMAGE_DIFF_NOTE_ERROR,
|
ADD_IMAGE_DIFF_NOTE_ERROR,
|
||||||
UPDATE_IMAGE_DIFF_NOTE_ERROR,
|
UPDATE_IMAGE_DIFF_NOTE_ERROR,
|
||||||
} from '~/design_management/utils/error_messages';
|
} from '~/design_management/utils/error_messages';
|
||||||
import createFlash from '~/flash';
|
import { createAlert } from '~/flash';
|
||||||
import design from '../mock_data/design';
|
import design from '../mock_data/design';
|
||||||
|
|
||||||
jest.mock('~/flash.js');
|
jest.mock('~/flash.js');
|
||||||
|
@ -32,10 +32,10 @@ describe('Design Management cache update', () => {
|
||||||
${'updateStoreAfterUploadDesign'} | ${updateStoreAfterUploadDesign} | ${mockErrors[0]} | ${[]}
|
${'updateStoreAfterUploadDesign'} | ${updateStoreAfterUploadDesign} | ${mockErrors[0]} | ${[]}
|
||||||
${'updateStoreAfterUpdateImageDiffNote'} | ${updateStoreAfterRepositionImageDiffNote} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR} | ${[]}
|
${'updateStoreAfterUpdateImageDiffNote'} | ${updateStoreAfterRepositionImageDiffNote} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR} | ${[]}
|
||||||
`('$fnName handles errors in response', ({ subject, extraArgs, errorMessage }) => {
|
`('$fnName handles errors in response', ({ subject, extraArgs, errorMessage }) => {
|
||||||
expect(createFlash).not.toHaveBeenCalled();
|
expect(createAlert).not.toHaveBeenCalled();
|
||||||
expect(() => subject(mockStore, { errors: mockErrors }, {}, ...extraArgs)).toThrow();
|
expect(() => subject(mockStore, { errors: mockErrors }, {}, ...extraArgs)).toThrow();
|
||||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
expect(createAlert).toHaveBeenCalledTimes(1);
|
||||||
expect(createFlash).toHaveBeenCalledWith({ message: errorMessage });
|
expect(createAlert).toHaveBeenCalledWith({ message: errorMessage });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -848,289 +848,4 @@ RSpec.describe SearchHelper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.search_navigation' do
|
|
||||||
using RSpec::Parameterized::TableSyntax
|
|
||||||
let(:user) { build(:user) }
|
|
||||||
let(:project) { build(:project) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
|
|
||||||
allow(self).to receive(:current_user).and_return(user)
|
|
||||||
allow(self).to receive(:can?).and_return(true)
|
|
||||||
allow(self).to receive(:project_search_tabs?).and_return(false)
|
|
||||||
allow(self).to receive(:feature_flag_tab_enabled?).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'projects' do
|
|
||||||
where(:global_project, :condition) do
|
|
||||||
nil | true
|
|
||||||
ref(:project) | false
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
@project = global_project
|
|
||||||
|
|
||||||
expect(search_navigation[:projects][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'code' do
|
|
||||||
where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :project_search_tabs, :condition) do
|
|
||||||
false | false | false | false
|
|
||||||
true | true | true | true
|
|
||||||
true | false | false | true
|
|
||||||
false | true | false | true
|
|
||||||
false | false | true | true
|
|
||||||
true | false | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
|
|
||||||
allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_code_tab).and_return(feature_flag_tab_enabled)
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:blobs).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:blobs][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'epics' do
|
|
||||||
where(:global_project, :show_epics, :condition) do
|
|
||||||
nil | false | false
|
|
||||||
ref(:project) | true | false
|
|
||||||
ref(:project) | false | false
|
|
||||||
nil | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
@project = global_project
|
|
||||||
allow(search_service).to receive(:show_epics?).and_return(show_epics)
|
|
||||||
|
|
||||||
expect(search_navigation[:epics][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'issues' do
|
|
||||||
where(:feature_flag_tab_enabled, :project_search_tabs, :condition) do
|
|
||||||
false | false | false
|
|
||||||
true | true | true
|
|
||||||
true | false | true
|
|
||||||
false | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_issues_tab).and_return(feature_flag_tab_enabled)
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:issues).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:issues][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'merge requests' do
|
|
||||||
where(:feature_flag_tab_enabled, :project_search_tabs, :condition) do
|
|
||||||
false | false | false
|
|
||||||
true | true | true
|
|
||||||
true | false | true
|
|
||||||
false | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_merge_requests_tab).and_return(feature_flag_tab_enabled)
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:merge_requests).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:merge_requests][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'wiki' do
|
|
||||||
where(:project_search_tabs, :show_elasticsearch_tabs, :condition) do
|
|
||||||
false | false | false
|
|
||||||
true | true | true
|
|
||||||
true | false | true
|
|
||||||
false | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:wiki).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:wiki_blobs][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'commits' do
|
|
||||||
where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :project_search_tabs, :condition) do
|
|
||||||
false | false | false | false
|
|
||||||
true | true | true | true
|
|
||||||
true | false | false | false
|
|
||||||
false | true | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
|
|
||||||
allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_commits_tab).and_return(feature_flag_tab_enabled)
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:commits).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:commits][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'comments' do
|
|
||||||
where(:show_elasticsearch_tabs, :project_search_tabs, :condition) do
|
|
||||||
true | true | true
|
|
||||||
false | false | false
|
|
||||||
true | false | true
|
|
||||||
false | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:notes).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:notes][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'milestones' do
|
|
||||||
where(:global_project, :project_search_tabs, :condition) do
|
|
||||||
ref(:project) | true | true
|
|
||||||
nil | false | true
|
|
||||||
ref(:project) | false | false
|
|
||||||
nil | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
@project = global_project
|
|
||||||
allow(self).to receive(:project_search_tabs?).with(:milestones).and_return(project_search_tabs)
|
|
||||||
|
|
||||||
expect(search_navigation[:milestones][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'users' do
|
|
||||||
where(:show_user_search_tab, :condition) do
|
|
||||||
true | true
|
|
||||||
false | false
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
allow(self).to receive(:show_user_search_tab?).and_return(show_user_search_tab)
|
|
||||||
|
|
||||||
expect(search_navigation[:users][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'snippet_titles' do
|
|
||||||
where(:global_project, :global_show_snippets, :condition) do
|
|
||||||
ref(:project) | true | false
|
|
||||||
nil | false | false
|
|
||||||
ref(:project) | false | false
|
|
||||||
nil | true | true
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'data item condition is set correctly' do
|
|
||||||
@show_snippets = global_show_snippets
|
|
||||||
@project = global_project
|
|
||||||
|
|
||||||
expect(search_navigation[:snippet_titles][:condition]).to eq(condition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.search_navigation_json' do
|
|
||||||
using RSpec::Parameterized::TableSyntax
|
|
||||||
context 'data' do
|
|
||||||
example_data_1 = {
|
|
||||||
projects: { label: _("Projects"), condition: true },
|
|
||||||
blobs: { label: _("Code"), condition: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
example_data_2 = {
|
|
||||||
projects: { label: _("Projects"), condition: false },
|
|
||||||
blobs: { label: _("Code"), condition: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
example_data_3 = {
|
|
||||||
projects: { label: _("Projects"), condition: true },
|
|
||||||
blobs: { label: _("Code"), condition: true },
|
|
||||||
epics: { label: _("Epics"), condition: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
where(:data, :matcher) do
|
|
||||||
example_data_1 | -> { include("projects") }
|
|
||||||
example_data_2 | -> { eq("{}") }
|
|
||||||
example_data_3 | -> { include("projects", "blobs", "epics") }
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'converts correctly' do
|
|
||||||
allow(self).to receive(:search_navigation).with(no_args).and_return(data)
|
|
||||||
expect(search_navigation_json).to instance_exec(&matcher)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.search_filter_link_json' do
|
|
||||||
using RSpec::Parameterized::TableSyntax
|
|
||||||
|
|
||||||
context 'data' do
|
|
||||||
where(:scope, :label, :data, :search, :active_scope) do
|
|
||||||
"projects" | "Projects" | { qa_selector: 'projects_tab' } | nil | "projects"
|
|
||||||
"snippet_titles" | "Titles and Descriptions" | nil | { snippets: "test" } | "code"
|
|
||||||
"projects" | "Projects" | { qa_selector: 'projects_tab' } | nil | "issue"
|
|
||||||
"snippet_titles" | "Titles and Descriptions" | nil | { snippets: "test" } | "snippet_titles"
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it 'converts correctly' do
|
|
||||||
@timeout = false
|
|
||||||
@scope = active_scope
|
|
||||||
@search_results = double
|
|
||||||
dummy_count = 1000
|
|
||||||
allow(self).to receive(:search_path).with(any_args).and_return("link test")
|
|
||||||
|
|
||||||
allow(@search_results).to receive(:formatted_count).with(scope).and_return(dummy_count)
|
|
||||||
allow(self).to receive(:search_count_path).with(any_args).and_return("test count link")
|
|
||||||
|
|
||||||
current_scope = scope == active_scope
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
label: label,
|
|
||||||
scope: scope,
|
|
||||||
data: data,
|
|
||||||
link: "link test",
|
|
||||||
active: current_scope
|
|
||||||
}
|
|
||||||
|
|
||||||
expected[:count] = dummy_count if current_scope
|
|
||||||
expected[:count_link] = "test count link" unless current_scope
|
|
||||||
|
|
||||||
expect(search_filter_link_json(scope, label, data, search)).to eq(expected)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -591,11 +591,11 @@ RSpec.describe Gitlab::GithubImport::Client do
|
||||||
|
|
||||||
describe 'search' do
|
describe 'search' do
|
||||||
let(:client) { described_class.new('foo') }
|
let(:client) { described_class.new('foo') }
|
||||||
let(:user) { double(:user, login: 'user') }
|
let(:user) { { login: 'user' } }
|
||||||
let(:org1) { double(:org, login: 'org1') }
|
let(:org1) { { login: 'org1' } }
|
||||||
let(:org2) { double(:org, login: 'org2') }
|
let(:org2) { { login: 'org2' } }
|
||||||
let(:repo1) { double(:repo, full_name: 'repo1') }
|
let(:repo1) { { full_name: 'repo1' } }
|
||||||
let(:repo2) { double(:repo, full_name: 'repo2') }
|
let(:repo2) { { full_name: 'repo2' } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(client)
|
allow(client)
|
||||||
|
|
|
@ -105,7 +105,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
||||||
'ci_templates',
|
'ci_templates',
|
||||||
'quickactions',
|
'quickactions',
|
||||||
'pipeline_authoring',
|
'pipeline_authoring',
|
||||||
'epics_usage',
|
|
||||||
'secure',
|
'secure',
|
||||||
'importer',
|
'importer',
|
||||||
'geo',
|
'geo',
|
||||||
|
|
Loading…
Reference in a new issue