summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-09 15:10:05 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-09 15:10:05 +0000
commitc0f42c6d662b776777afbf79ba72d8e833b8de48 (patch)
treed94d38bccd5297f59522090fd3c814d9264a1cc9
parentf4d6d3ec77286fa64810bd6a25c58671e0deedaf (diff)
downloadgitlab-ce-c0f42c6d662b776777afbf79ba72d8e833b8de48.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/logs/components/environment_logs.vue25
-rw-r--r--app/assets/javascripts/logs/constants.js1
-rw-r--r--app/assets/javascripts/logs/stores/actions.js32
-rw-r--r--app/assets/javascripts/logs/stores/getters.js13
-rw-r--r--app/assets/javascripts/logs/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/logs/stores/mutations.js25
-rw-r--r--app/assets/javascripts/logs/stores/state.js10
-rw-r--r--app/assets/javascripts/nav/components/responsive_app.vue79
-rw-r--r--app/assets/javascripts/nav/components/responsive_header.vue37
-rw-r--r--app/assets/javascripts/nav/components/responsive_home.vue62
-rw-r--r--app/assets/javascripts/nav/components/top_nav_container_view.vue11
-rw-r--r--app/assets/javascripts/nav/components/top_nav_menu_item.vue14
-rw-r--r--app/assets/javascripts/nav/components/top_nav_menu_sections.vue1
-rw-r--r--app/assets/javascripts/nav/components/top_nav_new_dropdown.vue55
-rw-r--r--app/assets/javascripts/nav/utils/reset_menu_items_active.js14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue11
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss9
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss9
-rw-r--r--app/finders/ci/pipelines_for_merge_request_finder.rb2
-rw-r--r--app/graphql/resolvers/ci/runners_resolver.rb4
-rw-r--r--app/helpers/nav/top_nav_helper.rb45
-rw-r--r--app/models/ci/runner.rb9
-rw-r--r--app/models/packages/package.rb16
-rw-r--r--app/views/layouts/header/_default.html.haml13
-rw-r--r--app/views/layouts/nav/_top_nav_responsive.html.haml2
-rw-r--r--app/views/notify/_failed_builds.html.haml4
-rw-r--r--app/views/notify/pipeline_failed_email.text.erb2
-rw-r--r--config/application.rb26
-rw-r--r--config/feature_flags/development/arel_package_scopes.yml8
-rw-r--r--config/feature_flags/development/use_distinct_in_shas_cte.yml2
-rw-r--r--config/metrics/counts_28d/20210216184941_i_ecosystem_jira_service_close_issue_monthly.yml14
-rw-r--r--config/metrics/counts_28d/20210216184945_i_ecosystem_jira_service_cross_reference_monthly.yml14
-rw-r--r--config/metrics/counts_28d/20210216184949_i_ecosystem_jira_service_list_issues_monthly.yml16
-rw-r--r--config/metrics/counts_28d/20210216184953_i_ecosystem_jira_service_create_issue_monthly.yml16
-rw-r--r--config/metrics/counts_28d/20210216184957_ecosystem_total_unique_counts_monthly.yml14
-rw-r--r--config/metrics/counts_7d/20210216184939_i_ecosystem_jira_service_close_issue_weekly.yml18
-rw-r--r--config/metrics/counts_7d/20210216184943_i_ecosystem_jira_service_cross_reference_weekly.yml18
-rw-r--r--config/metrics/counts_7d/20210216184955_ecosystem_total_unique_counts_weekly.yml18
-rw-r--r--config/metrics/counts_all/20210216182547_projects_datadog_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182549_groups_datadog_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182551_templates_datadog_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182553_instances_datadog_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182555_projects_inheriting_datadog_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182557_groups_inheriting_datadog_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182614_projects_ewm_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182616_groups_ewm_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182618_templates_ewm_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182620_instances_ewm_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182622_projects_inheriting_ewm_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182623_groups_inheriting_ewm_active.yml14
-rw-r--r--config/metrics/counts_all/20210216182722_projects_mock_ci_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182724_groups_mock_ci_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182726_templates_mock_ci_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182728_instances_mock_ci_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182730_projects_inheriting_mock_ci_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182732_groups_inheriting_mock_ci_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182734_projects_mock_monitoring_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182736_groups_mock_monitoring_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182738_templates_mock_monitoring_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182739_instances_mock_monitoring_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182741_projects_inheriting_mock_monitoring_active.yml17
-rw-r--r--config/metrics/counts_all/20210216182743_groups_inheriting_mock_monitoring_active.yml17
-rw-r--r--config/routes.rb8
-rw-r--r--db/migrate/20210602122233_add_runners_description_index.rb17
-rw-r--r--db/schema_migrations/202106021222331
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/geo/disaster_recovery/index.md19
-rw-r--r--doc/api/graphql/getting_started.md123
-rw-r--r--doc/api/graphql/reference/index.md31
-rw-r--r--doc/api/groups.md22
-rw-r--r--doc/api/runners.md2
-rw-r--r--doc/development/usage_ping/dictionary.md250
-rw-r--r--doc/user/admin_area/analytics/dev_ops_report.md21
-rw-r--r--doc/user/admin_area/analytics/img/admin_devops_adoption_v14_0.pngbin0 -> 63094 bytes
-rw-r--r--doc/user/group/devops_adoption/img/group_devops_adoption_v13_11.pngbin58389 -> 0 bytes
-rw-r--r--doc/user/group/devops_adoption/img/group_devops_adoption_v14_0.pngbin0 -> 64879 bytes
-rw-r--r--doc/user/group/devops_adoption/index.md21
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/group_avatar.rb21
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb2
-rw-r--r--spec/features/nav/top_nav_responsive_spec.rb23
-rw-r--r--spec/features/projects/environments_pod_logs_spec.rb2
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js3
-rw-r--r--spec/frontend/logs/mock_data.js27
-rw-r--r--spec/frontend/logs/stores/actions_spec.js27
-rw-r--r--spec/frontend/logs/stores/getters_spec.js48
-rw-r--r--spec/frontend/logs/stores/mutations_spec.js36
-rw-r--r--spec/frontend/nav/components/responsive_app_spec.js92
-rw-r--r--spec/frontend/nav/components/responsive_header_spec.js67
-rw-r--r--spec/frontend/nav/components/responsive_home_spec.js137
-rw-r--r--spec/frontend/nav/components/top_nav_container_view_spec.js6
-rw-r--r--spec/frontend/nav/components/top_nav_menu_item_spec.js40
-rw-r--r--spec/frontend/nav/components/top_nav_menu_sections_spec.js8
-rw-r--r--spec/frontend/nav/components/top_nav_new_dropdown_spec.js122
-rw-r--r--spec/frontend/nav/mock_data.js4
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js4
-rw-r--r--spec/graphql/resolvers/ci/runners_resolver_spec.rb46
-rw-r--r--spec/helpers/nav/top_nav_helper_spec.rb60
-rw-r--r--spec/models/ci/runner_spec.rb8
-rw-r--r--spec/models/packages/package_spec.rb15
-rw-r--r--spec/requests/api/group_avatar_spec.rb64
-rw-r--r--spec/requests/oauth/tokens_controller_spec.rb71
-rw-r--r--spec/requests/openid_connect_spec.rb110
-rw-r--r--vendor/project_templates/cluster_management.tar.gzbin4051 -> 10293 bytes
108 files changed, 1857 insertions, 768 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index bfd59c5ffca..5cf4124a5bb 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-22f7db01953debe8e7c1c46ff2b1ffb5a143c566
+dc956109aa3e352cadc55ebba267b43fdd4fdc27
diff --git a/Gemfile b/Gemfile
index 12e82216a40..d99cd5970e4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -129,7 +129,7 @@ gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.3'
-gem 'gitlab-fog-azure-rm', '~> 1.0.1', require: false
+gem 'gitlab-fog-azure-rm', '~> 1.1.1', require: false
# for Google storage
gem 'google-api-client', '~> 0.33'
diff --git a/Gemfile.lock b/Gemfile.lock
index 06dfaccac3d..a1fd31680a1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -457,7 +457,7 @@ GEM
activesupport (>= 3.0)
request_store (>= 1.0)
scientist (~> 1.6, >= 1.6.0)
- gitlab-fog-azure-rm (1.0.1)
+ gitlab-fog-azure-rm (1.1.1)
azure-storage-blob (~> 2.0)
azure-storage-common (~> 2.0)
fog-core (= 2.1.0)
@@ -1474,7 +1474,7 @@ DEPENDENCIES
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.0.0)
gitlab-experiment (~> 0.5.4)
- gitlab-fog-azure-rm (~> 1.0.1)
+ gitlab-fog-azure-rm (~> 1.1.1)
gitlab-fog-google (~> 1.13)
gitlab-labkit (~> 0.18.0)
gitlab-license (~> 1.5)
diff --git a/app/assets/javascripts/logs/components/environment_logs.vue b/app/assets/javascripts/logs/components/environment_logs.vue
index 5092c6905c4..39041aa1447 100644
--- a/app/assets/javascripts/logs/components/environment_logs.vue
+++ b/app/assets/javascripts/logs/components/environment_logs.vue
@@ -5,7 +5,6 @@ import {
GlDropdown,
GlDropdownSectionHeader,
GlDropdownItem,
- GlDropdownDivider,
GlInfiniteScroll,
} from '@gitlab/ui';
import { throttle } from 'lodash';
@@ -25,7 +24,6 @@ export default {
GlDropdown,
GlDropdownSectionHeader,
GlDropdownItem,
- GlDropdownDivider,
GlInfiniteScroll,
LogSimpleFilters,
LogAdvancedFilters,
@@ -66,7 +64,7 @@ export default {
};
},
computed: {
- ...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods', 'managedApps']),
+ ...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods']),
...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']),
showLoader() {
@@ -88,15 +86,12 @@ export default {
});
this.fetchEnvironments(this.environmentsPath);
- this.fetchManagedApps(this.clustersPath);
},
methods: {
...mapActions('environmentLogs', [
'setInitData',
'showEnvironment',
- 'showManagedApp',
'fetchEnvironments',
- 'fetchManagedApps',
'refreshPodLogs',
'fetchMoreLogsPrepend',
'dismissRequestEnvironmentsError',
@@ -107,9 +102,6 @@ export default {
isCurrentEnvironment(envName) {
return envName === this.environments.current;
},
- isCurrentManagedApp(appName) {
- return appName === this.managedApps.current;
- },
topReached() {
if (!this.logs.isLoading) {
this.fetchMoreLogsPrepend();
@@ -173,7 +165,7 @@ export default {
<div class="flex-grow-0">
<gl-dropdown
id="environments-dropdown"
- :text="environments.current || managedApps.current"
+ :text="environments.current"
:disabled="environments.isLoading"
class="gl-mr-3 gl-mb-3 gl-display-flex gl-md-display-block js-environments-dropdown"
>
@@ -189,19 +181,6 @@ export default {
>
{{ env.name }}
</gl-dropdown-item>
- <gl-dropdown-divider />
- <gl-dropdown-section-header>
- {{ s__('Environments|Managed apps') }}
- </gl-dropdown-section-header>
- <gl-dropdown-item
- v-for="app in managedApps.options"
- :key="app.id"
- :is-check-item="true"
- :is-checked="isCurrentManagedApp(app.name)"
- @click="showManagedApp(app.name)"
- >
- {{ app.name }}
- </gl-dropdown-item>
</gl-dropdown>
</div>
diff --git a/app/assets/javascripts/logs/constants.js b/app/assets/javascripts/logs/constants.js
index 0cdef53df34..abc4d6679a0 100644
--- a/app/assets/javascripts/logs/constants.js
+++ b/app/assets/javascripts/logs/constants.js
@@ -13,5 +13,4 @@ export const tracking = {
export const logExplorerOptions = {
environments: 'environments',
- managedApps: 'managedApps',
};
diff --git a/app/assets/javascripts/logs/stores/actions.js b/app/assets/javascripts/logs/stores/actions.js
index c3dc9f4bc12..56b832de9b8 100644
--- a/app/assets/javascripts/logs/stores/actions.js
+++ b/app/assets/javascripts/logs/stores/actions.js
@@ -25,15 +25,9 @@ const requestUntilData = (url, params) =>
const requestLogsUntilData = ({ commit, state }) => {
const params = {};
- const type = state.environments.current
- ? logExplorerOptions.environments
- : logExplorerOptions.managedApps;
+ const type = logExplorerOptions.environments;
const selectedObj = state[type].options.find(({ name }) => name === state[type].current);
-
- const path =
- type === logExplorerOptions.environments
- ? selectedObj.logs_api_path
- : selectedObj.gitlab_managed_apps_logs_path;
+ const path = selectedObj.logs_api_path;
if (state.pods.current) {
params.pod_name = state.pods.current;
@@ -106,11 +100,6 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => {
dispatch('fetchLogs', tracking.ENVIRONMENT_SELECTED);
};
-export const showManagedApp = ({ dispatch, commit }, managedApp) => {
- commit(types.SET_MANAGED_APP, managedApp);
- dispatch('fetchLogs', tracking.MANAGED_APP_SELECTED);
-};
-
export const refreshPodLogs = ({ dispatch, commit }) => {
commit(types.REFRESH_POD_LOGS);
dispatch('fetchLogs', tracking.REFRESH_POD_LOGS);
@@ -135,23 +124,6 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
});
};
-/**
- * Fetch managed apps data
- * @param {Object} store
- * @param {String} clustersPath
- */
-
-export const fetchManagedApps = ({ commit }, clustersPath) => {
- return axios
- .get(clustersPath)
- .then(({ data }) => {
- commit(types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, data.clusters);
- })
- .catch(() => {
- commit(types.RECEIVE_MANAGED_APPS_DATA_ERROR);
- });
-};
-
export const fetchLogs = ({ commit, state }, trackingLabel) => {
commit(types.REQUEST_LOGS_DATA);
diff --git a/app/assets/javascripts/logs/stores/getters.js b/app/assets/javascripts/logs/stores/getters.js
index 836e6e82385..bf71cfd8eb2 100644
--- a/app/assets/javascripts/logs/stores/getters.js
+++ b/app/assets/javascripts/logs/stores/getters.js
@@ -6,16 +6,9 @@ const mapTrace = ({ timestamp = null, pod = '', message = '' }) =>
export const trace = (state) => state.logs.lines.map(mapTrace).join('\n');
export const showAdvancedFilters = (state) => {
- if (state.environments.current) {
- const environment = state.environments.options.find(
- ({ name }) => name === state.environments.current,
- );
-
- return Boolean(environment?.enable_advanced_logs_querying);
- }
- const managedApp = state.managedApps.options.find(
- ({ name }) => name === state.managedApps.current,
+ const environment = state.environments.options.find(
+ ({ name }) => name === state.environments.current,
);
- return Boolean(managedApp?.enable_advanced_logs_querying);
+ return Boolean(environment?.enable_advanced_logs_querying);
};
diff --git a/app/assets/javascripts/logs/stores/mutation_types.js b/app/assets/javascripts/logs/stores/mutation_types.js
index eaa4b13f8bd..c1ed65ff48b 100644
--- a/app/assets/javascripts/logs/stores/mutation_types.js
+++ b/app/assets/javascripts/logs/stores/mutation_types.js
@@ -13,9 +13,6 @@ export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCC
export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR';
export const HIDE_REQUEST_ENVIRONMENTS_ERROR = 'HIDE_REQUEST_ENVIRONMENTS_ERROR';
-export const RECEIVE_MANAGED_APPS_DATA_SUCCESS = 'RECEIVE_MANAGED_APPS_DATA_SUCCESS';
-export const RECEIVE_MANAGED_APPS_DATA_ERROR = 'RECEIVE_MANAGED_APPS_DATA_ERROR';
-
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
diff --git a/app/assets/javascripts/logs/stores/mutations.js b/app/assets/javascripts/logs/stores/mutations.js
index 21031838adf..6736d7204b4 100644
--- a/app/assets/javascripts/logs/stores/mutations.js
+++ b/app/assets/javascripts/logs/stores/mutations.js
@@ -32,9 +32,6 @@ export default {
// Clear current pod options
state.pods.current = null;
state.pods.options = [];
-
- // Clear current managedApps options
- state.managedApps.current = null;
},
[types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = [];
@@ -110,26 +107,4 @@ export default {
[types.RECEIVE_PODS_DATA_ERROR](state) {
state.pods.options = [];
},
- // Managed apps data
- [types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, apps) {
- state.managedApps.options = apps.filter(
- ({ gitlab_managed_apps_logs_path }) => gitlab_managed_apps_logs_path, // eslint-disable-line babel/camelcase
- );
- state.managedApps.isLoading = false;
- },
- [types.RECEIVE_MANAGED_APPS_DATA_ERROR](state) {
- state.managedApps.options = [];
- state.managedApps.isLoading = false;
- state.managedApps.fetchError = true;
- },
- [types.SET_MANAGED_APP](state, managedApp) {
- state.managedApps.current = managedApp;
-
- // Clear current pod options
- state.pods.current = null;
- state.pods.options = [];
-
- // Clear current environment options
- state.environments.current = null;
- },
};
diff --git a/app/assets/javascripts/logs/stores/state.js b/app/assets/javascripts/logs/stores/state.js
index e2028621ac8..83080589362 100644
--- a/app/assets/javascripts/logs/stores/state.js
+++ b/app/assets/javascripts/logs/stores/state.js
@@ -31,16 +31,6 @@ export default () => ({
},
/**
- * Managed apps list information
- */
- managedApps: {
- options: [],
- isLoading: false,
- current: null,
- fetchError: false,
- },
-
- /**
* Logs including trace
*/
logs: {
diff --git a/app/assets/javascripts/nav/components/responsive_app.vue b/app/assets/javascripts/nav/components/responsive_app.vue
index 01b0fa370b5..cef1a14df19 100644
--- a/app/assets/javascripts/nav/components/responsive_app.vue
+++ b/app/assets/javascripts/nav/components/responsive_app.vue
@@ -1,30 +1,101 @@
<script>
+import { FREQUENT_ITEMS_PROJECTS, FREQUENT_ITEMS_GROUPS } from '~/frequent_items/constants';
+import { BV_DROPDOWN_SHOW, BV_DROPDOWN_HIDE } from '~/lib/utils/constants';
+import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
import eventHub, { EVENT_RESPONSIVE_TOGGLE } from '../event_hub';
-
-const TEMPORARY_PLACEHOLDER = 'Placeholder for responsive top nav';
+import { resetMenuItemsActive } from '../utils/reset_menu_items_active';
+import ResponsiveHeader from './responsive_header.vue';
+import ResponsiveHome from './responsive_home.vue';
+import TopNavContainerView from './top_nav_container_view.vue';
export default {
+ components: {
+ KeepAliveSlots,
+ ResponsiveHeader,
+ ResponsiveHome,
+ TopNavContainerView,
+ },
props: {
navData: {
type: Object,
required: true,
},
},
+ data() {
+ return {
+ activeView: 'home',
+ hasMobileOverlay: false,
+ };
+ },
+ computed: {
+ nav() {
+ return resetMenuItemsActive(this.navData);
+ },
+ },
created() {
eventHub.$on(EVENT_RESPONSIVE_TOGGLE, this.onToggle);
+ this.$root.$on(BV_DROPDOWN_SHOW, this.showMobileOverlay);
+ this.$root.$on(BV_DROPDOWN_HIDE, this.hideMobileOverlay);
},
beforeDestroy() {
eventHub.$off(EVENT_RESPONSIVE_TOGGLE, this.onToggle);
+ this.$root.$off(BV_DROPDOWN_SHOW, this.showMobileOverlay);
+ this.$root.$off(BV_DROPDOWN_HIDE, this.hideMobileOverlay);
},
methods: {
onToggle() {
document.body.classList.toggle('top-nav-responsive-open');
},
+ onMenuItemClick({ view }) {
+ if (view) {
+ this.activeView = view;
+ }
+ },
+ showMobileOverlay() {
+ this.hasMobileOverlay = true;
+ },
+ hideMobileOverlay() {
+ this.hasMobileOverlay = false;
+ },
},
- TEMPORARY_PLACEHOLDER,
+ FREQUENT_ITEMS_PROJECTS,
+ FREQUENT_ITEMS_GROUPS,
};
</script>
<template>
- <p>{{ $options.TEMPORARY_PLACEHOLDER }}</p>
+ <div>
+ <div
+ class="mobile-overlay"
+ :class="{ 'mobile-nav-open': hasMobileOverlay }"
+ data-testid="mobile-overlay"
+ ></div>
+ <keep-alive-slots :slot-key="activeView">
+ <template #home>
+ <responsive-home :nav-data="nav" @menu-item-click="onMenuItemClick" />
+ </template>
+ <template #projects>
+ <responsive-header @menu-item-click="onMenuItemClick">
+ {{ __('Projects') }}
+ </responsive-header>
+ <top-nav-container-view
+ :frequent-items-dropdown-type="$options.FREQUENT_ITEMS_PROJECTS.namespace"
+ :frequent-items-vuex-module="$options.FREQUENT_ITEMS_PROJECTS.vuexModule"
+ container-class="gl-px-3"
+ v-bind="nav.views.projects"
+ />
+ </template>
+ <template #groups>
+ <responsive-header @menu-item-click="onMenuItemClick">
+ {{ __('Groups') }}
+ </responsive-header>
+ <top-nav-container-view
+ :frequent-items-dropdown-type="$options.FREQUENT_ITEMS_GROUPS.namespace"
+ :frequent-items-vuex-module="$options.FREQUENT_ITEMS_GROUPS.vuexModule"
+ container-class="gl-px-3"
+ v-bind="nav.views.groups"
+ />
+ </template>
+ </keep-alive-slots>
+ </div>
</template>
diff --git a/app/assets/javascripts/nav/components/responsive_header.vue b/app/assets/javascripts/nav/components/responsive_header.vue
new file mode 100644
index 00000000000..8a1d21993b7
--- /dev/null
+++ b/app/assets/javascripts/nav/components/responsive_header.vue
@@ -0,0 +1,37 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import TopNavMenuItem from './top_nav_menu_item.vue';
+
+export default {
+ components: {
+ TopNavMenuItem,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ computed: {
+ menuItem() {
+ return {
+ id: 'home',
+ view: 'home',
+ icon: 'angle-left',
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <header class="gl-py-4 gl-display-flex gl-align-items-center">
+ <top-nav-menu-item
+ v-gl-tooltip="{ title: s__('TopNav|Go back') }"
+ class="gl-p-3!"
+ :menu-item="menuItem"
+ icon-only
+ @click="$emit('menu-item-click', menuItem)"
+ />
+ <span class="gl-font-size-h2 gl-font-weight-bold gl-ml-2">
+ <slot></slot>
+ </span>
+ </header>
+</template>
diff --git a/app/assets/javascripts/nav/components/responsive_home.vue b/app/assets/javascripts/nav/components/responsive_home.vue
new file mode 100644
index 00000000000..c8f2f0bfb10
--- /dev/null
+++ b/app/assets/javascripts/nav/components/responsive_home.vue
@@ -0,0 +1,62 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import TopNavMenuItem from './top_nav_menu_item.vue';
+import TopNavMenuSections from './top_nav_menu_sections.vue';
+import TopNavNewDropdown from './top_nav_new_dropdown.vue';
+
+const NEW_VIEW = 'new';
+const SEARCH_VIEW = 'search';
+
+export default {
+ components: {
+ TopNavMenuItem,
+ TopNavMenuSections,
+ TopNavNewDropdown,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ navData: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ menuSections() {
+ return [
+ { id: 'primary', menuItems: this.navData.primary },
+ { id: 'secondary', menuItems: this.navData.secondary },
+ ].filter((x) => x.menuItems?.length);
+ },
+ newDropdownViewModel() {
+ return this.navData.views[NEW_VIEW];
+ },
+ searchMenuItem() {
+ return this.navData.views[SEARCH_VIEW];
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <header class="gl-display-flex gl-align-items-center gl-py-4 gl-pl-4">
+ <h1 class="gl-m-0 gl-font-size-h2 gl-reset-color gl-mr-auto">{{ __('Menu') }}</h1>
+ <top-nav-menu-item
+ v-if="searchMenuItem"
+ v-gl-tooltip="{ title: searchMenuItem.title }"
+ class="gl-ml-3"
+ :menu-item="searchMenuItem"
+ icon-only
+ />
+ <top-nav-new-dropdown
+ v-if="newDropdownViewModel"
+ v-gl-tooltip="{ title: newDropdownViewModel.title }"
+ :view-model="newDropdownViewModel"
+ class="gl-ml-3"
+ />
+ </header>
+ <top-nav-menu-sections class="gl-h-full" :sections="menuSections" v-on="$listeners" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/nav/components/top_nav_container_view.vue b/app/assets/javascripts/nav/components/top_nav_container_view.vue
index 4043ef1ae5b..6f98f85ff90 100644
--- a/app/assets/javascripts/nav/components/top_nav_container_view.vue
+++ b/app/assets/javascripts/nav/components/top_nav_container_view.vue
@@ -20,6 +20,11 @@ export default {
type: String,
required: true,
},
+ containerClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
linksPrimary: {
type: Array,
required: false,
@@ -50,7 +55,11 @@ export default {
<template>
<div class="top-nav-container-view gl-display-flex gl-flex-direction-column">
- <div class="frequent-items-dropdown-container gl-w-auto">
+ <div
+ class="frequent-items-dropdown-container gl-w-auto"
+ :class="containerClass"
+ data-testid="frequent-items-container"
+ >
<div class="frequent-items-dropdown-content gl-w-full! gl-pt-0!">
<vuex-module-provider :vuex-module="frequentItemsVuexModule">
<frequent-items-app v-bind="$attrs" />
diff --git a/app/assets/javascripts/nav/components/top_nav_menu_item.vue b/app/assets/javascripts/nav/components/top_nav_menu_item.vue
index 3675ee50fdf..08b2fbf2ed1 100644
--- a/app/assets/javascripts/nav/components/top_nav_menu_item.vue
+++ b/app/assets/javascripts/nav/components/top_nav_menu_item.vue
@@ -16,6 +16,11 @@ export default {
type: Object,
required: true,
},
+ iconOnly: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
dataAttrs() {
@@ -32,13 +37,16 @@ export default {
:href="menuItem.href"
class="top-nav-menu-item gl-display-block"
:class="[menuItem.css_class, { [$options.ACTIVE_CLASS]: menuItem.active }]"
+ :aria-label="menuItem.title"
v-bind="dataAttrs"
v-on="$listeners"
>
<span class="gl-display-flex">
- <gl-icon v-if="menuItem.icon" :name="menuItem.icon" class="gl-mr-2!" />
- {{ menuItem.title }}
- <gl-icon v-if="menuItem.view" name="chevron-right" class="gl-ml-auto" />
+ <gl-icon v-if="menuItem.icon" :name="menuItem.icon" :class="{ 'gl-mr-2!': !iconOnly }" />
+ <template v-if="!iconOnly">
+ {{ menuItem.title }}
+ <gl-icon v-if="menuItem.view" name="chevron-right" class="gl-ml-auto" />
+ </template>
</span>
</gl-button>
</template>
diff --git a/app/assets/javascripts/nav/components/top_nav_menu_sections.vue b/app/assets/javascripts/nav/components/top_nav_menu_sections.vue
index 407c79b1f42..442af512350 100644
--- a/app/assets/javascripts/nav/components/top_nav_menu_sections.vue
+++ b/app/assets/javascripts/nav/components/top_nav_menu_sections.vue
@@ -54,6 +54,7 @@ export default {
:key="menuItem.id"
:menu-item="menuItem"
data-testid="menu-item"
+ class="gl-w-full"
:class="{ 'gl-mt-1': menuItemIndex > 0 }"
@click="onClick(menuItem)"
/>
diff --git a/app/assets/javascripts/nav/components/top_nav_new_dropdown.vue b/app/assets/javascripts/nav/components/top_nav_new_dropdown.vue
new file mode 100644
index 00000000000..154bed81854
--- /dev/null
+++ b/app/assets/javascripts/nav/components/top_nav_new_dropdown.vue
@@ -0,0 +1,55 @@
+<script>
+import { GlDropdown, GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ },
+ props: {
+ viewModel: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ sections() {
+ return this.viewModel.menu_sections || [];
+ },
+ showHeaders() {
+ return this.sections.length > 1;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown
+ toggle-class="top-nav-menu-item"
+ icon="plus"
+ :text="viewModel.title"
+ category="tertiary"
+ text-sr-only
+ no-caret
+ right
+ >
+ <template v-for="({ title, menu_items }, index) in sections">
+ <gl-dropdown-divider v-if="index > 0" :key="`${index}_divider`" data-testid="divider" />
+ <gl-dropdown-section-header v-if="showHeaders" :key="`${index}_header`" data-testid="header">
+ {{ title }}
+ </gl-dropdown-section-header>
+ <template v-for="menuItem in menu_items">
+ <gl-dropdown-item
+ :key="`${index}_item_${menuItem.id}`"
+ link-class="top-nav-menu-item"
+ :href="menuItem.href"
+ data-testid="item"
+ >
+ {{ menuItem.title }}
+ </gl-dropdown-item>
+ </template>
+ </template>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/nav/utils/reset_menu_items_active.js b/app/assets/javascripts/nav/utils/reset_menu_items_active.js
new file mode 100644
index 00000000000..9b5d8e97c9c
--- /dev/null
+++ b/app/assets/javascripts/nav/utils/reset_menu_items_active.js
@@ -0,0 +1,14 @@
+const resetActiveInArray = (arr) => arr?.map((menuItem) => ({ ...menuItem, active: false }));
+
+/**
+ * This method sets `active: false` for the menu items within the given nav data.
+ *
+ * @returns navData with the menu items updated with `active: false`
+ */
+export const resetMenuItemsActive = ({ primary, secondary, ...navData }) => {
+ return {
+ ...navData,
+ primary: resetActiveInArray(primary),
+ secondary: resetActiveInArray(secondary),
+ };
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
index d50d97e3570..9268e426954 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -1,15 +1,14 @@
<script>
-import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '../../locale';
export default {
i18n: {
- removesBranchText: __('%{strongStart}Deletes%{strongEnd} source branch'),
+ removesBranchText: __('The source branch will be deleted'),
tooltipTitle: __('A user with write access to the source branch selected this option'),
},
components: {
GlIcon,
- GlSprintf,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -20,11 +19,7 @@ export default {
<template>
<p v-once class="mr-info-list gl-ml-7 gl-pb-5 gl-mb-0">
<span class="status-text">
- <gl-sprintf :message="$options.i18n.removesBranchText">
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
+ {{ $options.i18n.removesBranchText }}
</span>
<gl-icon
v-gl-tooltip.hover
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index d5e349ab72c..a16ae62b1c3 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -497,6 +497,9 @@ body {
color: #dbdbdb;
vertical-align: baseline;
}
+.gl-font-sm {
+ font-size: 12px;
+}
.dropdown {
position: relative;
}
@@ -2179,6 +2182,12 @@ body.gl-dark {
margin-left: 0 !important;
margin-right: 0 !important;
}
+.gl-font-sm {
+ font-size: 0.75rem;
+}
+.gl-font-weight-bold {
+ font-weight: 600;
+}
@import "startup/cloaking";
@include cloak-startup-scss(none);
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 9f15b107bc7..7ec99a6588f 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -482,6 +482,9 @@ body {
color: #525252;
vertical-align: baseline;
}
+.gl-font-sm {
+ font-size: 12px;
+}
.dropdown {
position: relative;
}
@@ -1962,6 +1965,12 @@ body.sidebar-refactoring
margin-left: 0 !important;
margin-right: 0 !important;
}
+.gl-font-sm {
+ font-size: 0.75rem;
+}
+.gl-font-weight-bold {
+ font-weight: 600;
+}
@import "startup/cloaking";
@include cloak-startup-scss(none);
diff --git a/app/finders/ci/pipelines_for_merge_request_finder.rb b/app/finders/ci/pipelines_for_merge_request_finder.rb
index be65b1f6b3c..6c5038128f8 100644
--- a/app/finders/ci/pipelines_for_merge_request_finder.rb
+++ b/app/finders/ci/pipelines_for_merge_request_finder.rb
@@ -48,7 +48,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def pipelines_using_cte
sha_relation = merge_request.all_commits.select(:sha)
- sha_relation = sha_relation.distinct if Feature.enabled?(:use_distinct_in_shas_cte)
+ sha_relation = sha_relation.distinct if Feature.enabled?(:use_distinct_in_shas_cte, default_enabled: :yaml)
cte = Gitlab::SQL::CTE.new(:shas, sha_relation)
diff --git a/app/graphql/resolvers/ci/runners_resolver.rb b/app/graphql/resolvers/ci/runners_resolver.rb
index 710706325cc..3ad1e2780dd 100644
--- a/app/graphql/resolvers/ci/runners_resolver.rb
+++ b/app/graphql/resolvers/ci/runners_resolver.rb
@@ -17,6 +17,10 @@ module Resolvers
required: false,
description: 'Filter by tags associated with the runner (comma-separated or array).'
+ argument :search, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Filter by full token or partial text in description field.'
+
argument :sort, ::Types::Ci::RunnerSortEnum,
required: false,
description: 'Sort order of results.'
diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb
index 7abc1e5e902..911e8ac4bf7 100644
--- a/app/helpers/nav/top_nav_helper.rb
+++ b/app/helpers/nav/top_nav_helper.rb
@@ -4,21 +4,58 @@ module Nav
module TopNavHelper
PROJECTS_VIEW = :projects
GROUPS_VIEW = :groups
+ NEW_VIEW = :new
+ SEARCH_VIEW = :search
def top_nav_view_model(project:, group:)
builder = ::Gitlab::Nav::TopNavViewModelBuilder.new
- if current_user
- build_view_model(builder: builder, project: project, group: group)
- else
- build_anonymous_view_model(builder: builder)
+ build_base_view_model(builder: builder, project: project, group: group)
+
+ builder.build
+ end
+
+ def top_nav_responsive_view_model(project:, group:)
+ builder = ::Gitlab::Nav::TopNavViewModelBuilder.new
+
+ build_base_view_model(builder: builder, project: project, group: group)
+
+ new_view_model = new_dropdown_view_model(project: project, group: group)
+
+ if new_view_model
+ builder.add_view(NEW_VIEW, new_view_model)
+ end
+
+ if top_nav_show_search
+ builder.add_view(SEARCH_VIEW, ::Gitlab::Nav::TopNavMenuItem.build(**top_nav_search_menu_item_attrs))
end
builder.build
end
+ def top_nav_show_search
+ header_link?(:search)
+ end
+
+ def top_nav_search_menu_item_attrs
+ {
+ id: 'search',
+ title: _('Search'),
+ icon: 'search',
+ href: search_context.search_url
+ }
+ end
+
private
+ def build_base_view_model(builder:, project:, group:)
+ if current_user
+ build_view_model(builder: builder, project: project, group: group)
+ else
+ build_anonymous_view_model(builder: builder)
+ end
+ end
+
def build_anonymous_view_model(builder:)
# These come from `app/views/layouts/nav/_explore.html.ham`
if explore_nav_link?(:projects)
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 0d9bf401aeb..14ec3a6838c 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -168,18 +168,13 @@ module Ci
# Searches for runners matching the given query.
#
- # This method uses ILIKE on PostgreSQL.
- #
- # This method performs a *partial* match on tokens, thus a query for "a"
- # will match any runner where the token contains the letter "a". As a result
- # you should *not* use this method for non-admin purposes as otherwise users
- # might be able to query a list of all runners.
+ # This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens.
#
# query - The search query as a String.
#
# Returns an ActiveRecord::Relation.
def self.search(query)
- fuzzy_search(query, [:token, :description])
+ where(token: query).or(fuzzy_search(query, [:description]))
end
def self.online_contact_time_deadline
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index 68c3b45c24e..29d9b4f5699 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -147,23 +147,15 @@ class Packages::Package < ApplicationRecord
scope :order_by_package_file, -> { joins(:package_files).order('packages_package_files.created_at ASC') }
scope :order_project_path, -> do
- if Feature.enabled?(:arel_package_scopes)
- keyset_order = keyset_pagination_order(join_class: Project, column_name: :path, direction: :asc)
+ keyset_order = keyset_pagination_order(join_class: Project, column_name: :path, direction: :asc)
- joins(:project).reorder(keyset_order)
- else
- joins(:project).reorder('projects.path ASC, id ASC')
- end
+ joins(:project).reorder(keyset_order)
end
scope :order_project_path_desc, -> do
- if Feature.enabled?(:arel_package_scopes)
- keyset_order = keyset_pagination_order(join_class: Project, column_name: :path, direction: :desc)
+ keyset_order = keyset_pagination_order(join_class: Project, column_name: :path, direction: :desc)
- joins(:project).reorder(keyset_order)
- else
- joins(:project).reorder('projects.path DESC, id DESC')
- end
+ joins(:project).reorder(keyset_order)
end
after_commit :update_composer_cache, on: :destroy, if: -> { composer? }
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index a0f6f35a5be..03fe2323773 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -6,7 +6,7 @@
%a.gl-sr-only.gl-accessibility{ href: "#content-body" } Skip to content
.container-fluid
.header-content
- .title-container{ class: ('hide-when-menu-expanded' if !use_top_nav_redesign) }
+ .title-container.hide-when-menu-expanded
%h1.title
%span.gl-sr-only GitLab
= link_to root_path, title: _('Dashboard'), id: 'logo', **tracking_attrs('main_navigation', 'click_gitlab_logo_link', 'navigation') do
@@ -33,12 +33,13 @@
%ul.nav.navbar-nav
- if current_user
= render 'layouts/header/new_dropdown', class: ('gl-display-none gl-sm-display-block' if use_top_nav_redesign)
- - if header_link?(:search)
+ - if top_nav_show_search
+ - search_menu_item = top_nav_search_menu_item_attrs
%li.nav-item.d-none.d-lg-block.m-auto
= render 'layouts/search' unless current_controller?(:search)
%li.nav-item{ class: use_top_nav_redesign ? "gl-display-none gl-sm-display-inline-block gl-lg-display-none" : "gl-display-inline-block gl-lg-display-none" }
- = link_to search_context.search_url, title: _('Search'), aria: { label: _('Search') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = sprite_icon('search')
+ = link_to search_menu_item.fetch(:href), title: search_menu_item.fetch(:title), aria: { label: search_menu_item.fetch(:title) }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = sprite_icon(search_menu_item.fetch(:icon))
- if header_link?(:issues)
= nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
= link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues', aria: { label: _('Issues') },
@@ -120,9 +121,9 @@
%button.navbar-toggler.d-block.d-sm-none{ type: 'button', class: ('gl-border-none!' if use_top_nav_redesign) }
%span.sr-only= _('Toggle navigation')
- if use_top_nav_redesign
- %span.more-icon.gl-px-3
+ %span.more-icon.gl-px-3.gl-font-sm.gl-font-weight-bold
%span.gl-pr-2= _('Menu')
- = sprite_icon('dot-grid', size: 16)
+ = sprite_icon('hamburger', size: 16)
- else
= sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon')
= sprite_icon('close', size: 12, css_class: 'close-icon')
diff --git a/app/views/layouts/nav/_top_nav_responsive.html.haml b/app/views/layouts/nav/_top_nav_responsive.html.haml
index 701bc603391..0d122f1adff 100644
--- a/app/views/layouts/nav/_top_nav_responsive.html.haml
+++ b/app/views/layouts/nav/_top_nav_responsive.html.haml
@@ -1,7 +1,7 @@
- return unless Feature.enabled?(:combined_menu, current_user, default_enabled: :yaml)
- top_class = local_assigns.fetch(:class, nil)
-- view_model = top_nav_view_model(project: @project, group: @group)
+- view_model = top_nav_responsive_view_model(project: @project, group: @group)
.top-nav-responsive{ class: top_class }
#js-top-nav-responsive{ data: { view_model: view_model.to_json } }
diff --git a/app/views/notify/_failed_builds.html.haml b/app/views/notify/_failed_builds.html.haml
index 11cbd700258..afed3c95130 100644
--- a/app/views/notify/_failed_builds.html.haml
+++ b/app/views/notify/_failed_builds.html.haml
@@ -3,10 +3,10 @@
had
= failed.size
failed
- #{'build'.pluralize(failed.size)}.
+ #{'job'.pluralize(failed.size)}.
%tr.table-warning
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; border: 1px solid #ededed; border-bottom: 0; border-radius: 4px 4px 0 0; overflow: hidden; background-color: #fdf4f6; color: #d22852; font-size: 14px; line-height: 1.4; text-align: center; padding: 8px 16px;" }
- Failed builds
+ Failed jobs
%tr.section
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 0 16px; border: 1px solid #ededed; border-radius: 4px; overflow: hidden; border-top: 0; border-radius: 0 0 4px 4px;" }
%table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width: 100%; border-collapse: collapse;" }
diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb
index 1fe7d554bc3..6ab74bcfb1a 100644
--- a/app/views/notify/pipeline_failed_email.text.erb
+++ b/app/views/notify/pipeline_failed_email.text.erb
@@ -28,7 +28,7 @@ Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
<% failed = @pipeline.latest_statuses.failed -%>
-had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>.
+had <%= failed.size %> failed <%= 'job'.pluralize(failed.size) %>.
<% failed.each do |build| -%>
<%= render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %>
diff --git a/config/application.rb b/config/application.rb
index 010ef9fff1b..fe10afab8ce 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -312,11 +312,33 @@ module Gitlab
end
# Cross-origin requests must be enabled for the Authorization code with PKCE OAuth flow when used from a browser.
+ %w(/oauth/token /oauth/revoke).each do |oauth_path|
+ allow do
+ origins '*'
+ resource oauth_path,
+ headers: %w(Authorization),
+ credentials: false,
+ methods: %i(post)
+ end
+ end
+
+ # These are routes from doorkeeper-openid_connect:
+ # https://github.com/doorkeeper-gem/doorkeeper-openid_connect#routes
allow do
origins '*'
- resource '/oauth/token',
+ resource '/oauth/userinfo',
+ headers: %w(Authorization),
credentials: false,
- methods: [:post]
+ methods: %i(get head post)
+ end
+
+ %w(/oauth/discovery/keys /.well-known/openid-configuration /.well-known/webfinger).each do |openid_path|
+ allow do
+ origins '*'
+ resource openid_path,
+ credentials: false,
+ methods: %i(get head)
+ end
end
end
diff --git a/config/feature_flags/development/arel_package_scopes.yml b/config/feature_flags/development/arel_package_scopes.yml
deleted file mode 100644
index b1a712ffdfa..00000000000
--- a/config/feature_flags/development/arel_package_scopes.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: arel_package_scopes
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62042
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331306
-milestone: '13.12'
-type: development
-group: group::package
-default_enabled: false
diff --git a/config/feature_flags/development/use_distinct_in_shas_cte.yml b/config/feature_flags/development/use_distinct_in_shas_cte.yml
index 2b317f99781..b741b8d5f0c 100644
--- a/config/feature_flags/development/use_distinct_in_shas_cte.yml
+++ b/config/feature_flags/development/use_distinct_in_shas_cte.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330586
milestone: '13.12'
type: development
group: group::optimize
-default_enabled: false
+default_enabled: true
diff --git a/config/metrics/counts_28d/20210216184941_i_ecosystem_jira_service_close_issue_monthly.yml b/config/metrics/counts_28d/20210216184941_i_ecosystem_jira_service_close_issue_monthly.yml
index c5fa7e2dcf7..d5a96845a4d 100644
--- a/config/metrics/counts_28d/20210216184941_i_ecosystem_jira_service_close_issue_monthly.yml
+++ b/config/metrics/counts_28d/20210216184941_i_ecosystem_jira_service_close_issue_monthly.yml
@@ -1,16 +1,18 @@
---
key_path: redis_hll_counters.ecosystem.i_ecosystem_jira_service_close_issue_monthly
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Number of users closing Jira issues by month
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_28d/20210216184945_i_ecosystem_jira_service_cross_reference_monthly.yml b/config/metrics/counts_28d/20210216184945_i_ecosystem_jira_service_cross_reference_monthly.yml
index f4469c41dd3..81b6bd694c8 100644
--- a/config/metrics/counts_28d/20210216184945_i_ecosystem_jira_service_cross_reference_monthly.yml
+++ b/config/metrics/counts_28d/20210216184945_i_ecosystem_jira_service_cross_reference_monthly.yml
@@ -1,16 +1,18 @@
---
key_path: redis_hll_counters.ecosystem.i_ecosystem_jira_service_cross_reference_monthly
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Number of users that cross-referenced Jira issues by month
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_28d/20210216184949_i_ecosystem_jira_service_list_issues_monthly.yml b/config/metrics/counts_28d/20210216184949_i_ecosystem_jira_service_list_issues_monthly.yml
deleted file mode 100644
index dd114b37a99..00000000000
--- a/config/metrics/counts_28d/20210216184949_i_ecosystem_jira_service_list_issues_monthly.yml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-key_path: redis_hll_counters.ecosystem.i_ecosystem_jira_service_list_issues_monthly
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
-value_type: number
-status: data_available
-time_frame: 28d
-data_source: redis_hll
-distribution:
-- ce
-tier:
-- free
-skip_validation: true
diff --git a/config/metrics/counts_28d/20210216184953_i_ecosystem_jira_service_create_issue_monthly.yml b/config/metrics/counts_28d/20210216184953_i_ecosystem_jira_service_create_issue_monthly.yml
deleted file mode 100644
index 04f33c6f333..00000000000
--- a/config/metrics/counts_28d/20210216184953_i_ecosystem_jira_service_create_issue_monthly.yml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-key_path: redis_hll_counters.ecosystem.i_ecosystem_jira_service_create_issue_monthly
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
-value_type: number
-status: data_available
-time_frame: 28d
-data_source: redis_hll
-distribution:
-- ce
-tier:
-- free
-skip_validation: true
diff --git a/config/metrics/counts_28d/20210216184957_ecosystem_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20210216184957_ecosystem_total_unique_counts_monthly.yml
index 6c1d08964a9..edd25537070 100644
--- a/config/metrics/counts_28d/20210216184957_ecosystem_total_unique_counts_monthly.yml
+++ b/config/metrics/counts_28d/20210216184957_ecosystem_total_unique_counts_monthly.yml
@@ -1,16 +1,18 @@
---
key_path: redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Number of users performing actions on Jira issues by month
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20210216184939_i_ecosystem_jira_service_close_issue_weekly.yml b/config/metrics/counts_7d/20210216184939_i_ecosystem_jira_service_close_issue_weekly.yml
new file mode 100644
index 00000000000..e3a8d4b4b8e
--- /dev/null
+++ b/config/metrics/counts_7d/20210216184939_i_ecosystem_jira_service_close_issue_weekly.yml
@@ -0,0 +1,18 @@
+---
+key_path: redis_hll_counters.ecosystem.i_ecosystem_jira_service_close_issue_weekly
+description: Number of users closing Jira issues by week
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
+value_type: number
+status: data_available
+time_frame: 7d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20210216184943_i_ecosystem_jira_service_cross_reference_weekly.yml b/config/metrics/counts_7d/20210216184943_i_ecosystem_jira_service_cross_reference_weekly.yml
new file mode 100644
index 00000000000..91edf718033
--- /dev/null
+++ b/config/metrics/counts_7d/20210216184943_i_ecosystem_jira_service_cross_reference_weekly.yml
@@ -0,0 +1,18 @@
+---
+key_path: redis_hll_counters.ecosystem.i_ecosystem_jira_service_cross_reference_weekly
+description: Number of users that cross-referenced Jira issues by week
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
+value_type: number
+status: data_available
+time_frame: 7d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20210216184955_ecosystem_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20210216184955_ecosystem_total_unique_counts_weekly.yml
new file mode 100644
index 00000000000..71a7c0ebbf1
--- /dev/null
+++ b/config/metrics/counts_7d/20210216184955_ecosystem_total_unique_counts_weekly.yml
@@ -0,0 +1,18 @@
+---
+key_path: redis_hll_counters.ecosystem.ecosystem_total_unique_counts_weekly
+description: Number of users performing actions on Jira issues by week
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
+value_type: number
+status: data_available
+time_frame: 7d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182547_projects_datadog_active.yml b/config/metrics/counts_all/20210216182547_projects_datadog_active.yml
index a96a88e9dab..66ec14d7c4d 100644
--- a/config/metrics/counts_all/20210216182547_projects_datadog_active.yml
+++ b/config/metrics/counts_all/20210216182547_projects_datadog_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.projects_datadog_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: 'Count of projects with active integrations for Datadog'
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182549_groups_datadog_active.yml b/config/metrics/counts_all/20210216182549_groups_datadog_active.yml
index caf9633e57f..1bc3803d82f 100644
--- a/config/metrics/counts_all/20210216182549_groups_datadog_active.yml
+++ b/config/metrics/counts_all/20210216182549_groups_datadog_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.groups_datadog_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of groups with active integrations for Datadog
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182551_templates_datadog_active.yml b/config/metrics/counts_all/20210216182551_templates_datadog_active.yml
index 9d911c6becf..8842e75f875 100644
--- a/config/metrics/counts_all/20210216182551_templates_datadog_active.yml
+++ b/config/metrics/counts_all/20210216182551_templates_datadog_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.templates_datadog_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active service templates for Datadog
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182553_instances_datadog_active.yml b/config/metrics/counts_all/20210216182553_instances_datadog_active.yml
index 25f6a6468d7..be281f26290 100644
--- a/config/metrics/counts_all/20210216182553_instances_datadog_active.yml
+++ b/config/metrics/counts_all/20210216182553_instances_datadog_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.instances_datadog_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active instance-level integrations for Datadog
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182555_projects_inheriting_datadog_active.yml b/config/metrics/counts_all/20210216182555_projects_inheriting_datadog_active.yml
index 97bfc3d2301..2c2d2aa6cb1 100644
--- a/config/metrics/counts_all/20210216182555_projects_inheriting_datadog_active.yml
+++ b/config/metrics/counts_all/20210216182555_projects_inheriting_datadog_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.projects_inheriting_datadog_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active projects inheriting integrations for Datadog
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182557_groups_inheriting_datadog_active.yml b/config/metrics/counts_all/20210216182557_groups_inheriting_datadog_active.yml
index 6cbbcadbe04..324b461ceea 100644
--- a/config/metrics/counts_all/20210216182557_groups_inheriting_datadog_active.yml
+++ b/config/metrics/counts_all/20210216182557_groups_inheriting_datadog_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.groups_inheriting_datadog_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active groups inheriting integrations for Datadog
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182614_projects_ewm_active.yml b/config/metrics/counts_all/20210216182614_projects_ewm_active.yml
index a0c8d827951..057906ecffd 100644
--- a/config/metrics/counts_all/20210216182614_projects_ewm_active.yml
+++ b/config/metrics/counts_all/20210216182614_projects_ewm_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.projects_ewm_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: 'Count of projects with active integrations for EWM'
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182616_groups_ewm_active.yml b/config/metrics/counts_all/20210216182616_groups_ewm_active.yml
index d41905af3b6..164466c69ba 100644
--- a/config/metrics/counts_all/20210216182616_groups_ewm_active.yml
+++ b/config/metrics/counts_all/20210216182616_groups_ewm_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.groups_ewm_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of groups with active integrations for EWM
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182618_templates_ewm_active.yml b/config/metrics/counts_all/20210216182618_templates_ewm_active.yml
index 0ef26b5b144..3bda8e71ac6 100644
--- a/config/metrics/counts_all/20210216182618_templates_ewm_active.yml
+++ b/config/metrics/counts_all/20210216182618_templates_ewm_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.templates_ewm_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active service templates for EWM
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182620_instances_ewm_active.yml b/config/metrics/counts_all/20210216182620_instances_ewm_active.yml
index 29d73ebb943..57cda20793b 100644
--- a/config/metrics/counts_all/20210216182620_instances_ewm_active.yml
+++ b/config/metrics/counts_all/20210216182620_instances_ewm_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.instances_ewm_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active instance-level integrations for EWM
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182622_projects_inheriting_ewm_active.yml b/config/metrics/counts_all/20210216182622_projects_inheriting_ewm_active.yml
index 8b6da149dfa..108ff88ee52 100644
--- a/config/metrics/counts_all/20210216182622_projects_inheriting_ewm_active.yml
+++ b/config/metrics/counts_all/20210216182622_projects_inheriting_ewm_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.projects_inheriting_ewm_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active projects inheriting integrations for EWM
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182623_groups_inheriting_ewm_active.yml b/config/metrics/counts_all/20210216182623_groups_inheriting_ewm_active.yml
index 995849e5945..dd9902c0b41 100644
--- a/config/metrics/counts_all/20210216182623_groups_inheriting_ewm_active.yml
+++ b/config/metrics/counts_all/20210216182623_groups_inheriting_ewm_active.yml
@@ -1,16 +1,18 @@
---
key_path: counts.groups_inheriting_ewm_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active groups inheriting integrations for EWM
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
status: data_available
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182722_projects_mock_ci_active.yml b/config/metrics/counts_all/20210216182722_projects_mock_ci_active.yml
index 1e7377ed707..cce2dedb709 100644
--- a/config/metrics/counts_all/20210216182722_projects_mock_ci_active.yml
+++ b/config/metrics/counts_all/20210216182722_projects_mock_ci_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.projects_mock_ci_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of projects with active integrations for Mock CI
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182724_groups_mock_ci_active.yml b/config/metrics/counts_all/20210216182724_groups_mock_ci_active.yml
index adf7175c7c6..b41982786a9 100644
--- a/config/metrics/counts_all/20210216182724_groups_mock_ci_active.yml
+++ b/config/metrics/counts_all/20210216182724_groups_mock_ci_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.groups_mock_ci_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of groups with active integrations for Mock CI
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182726_templates_mock_ci_active.yml b/config/metrics/counts_all/20210216182726_templates_mock_ci_active.yml
index 654ffad1ba6..786ecefcf1c 100644
--- a/config/metrics/counts_all/20210216182726_templates_mock_ci_active.yml
+++ b/config/metrics/counts_all/20210216182726_templates_mock_ci_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.templates_mock_ci_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active service templates for Mock CI
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182728_instances_mock_ci_active.yml b/config/metrics/counts_all/20210216182728_instances_mock_ci_active.yml
index 41958f501b7..13d5f4cc809 100644
--- a/config/metrics/counts_all/20210216182728_instances_mock_ci_active.yml
+++ b/config/metrics/counts_all/20210216182728_instances_mock_ci_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.instances_mock_ci_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active instance-level integrations for Mock CI
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182730_projects_inheriting_mock_ci_active.yml b/config/metrics/counts_all/20210216182730_projects_inheriting_mock_ci_active.yml
index 63bafcd95d7..836d02ef768 100644
--- a/config/metrics/counts_all/20210216182730_projects_inheriting_mock_ci_active.yml
+++ b/config/metrics/counts_all/20210216182730_projects_inheriting_mock_ci_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.projects_inheriting_mock_ci_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active projects inheriting integrations for Mock CI
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182732_groups_inheriting_mock_ci_active.yml b/config/metrics/counts_all/20210216182732_groups_inheriting_mock_ci_active.yml
index 994e9665ff1..723399c5d04 100644
--- a/config/metrics/counts_all/20210216182732_groups_inheriting_mock_ci_active.yml
+++ b/config/metrics/counts_all/20210216182732_groups_inheriting_mock_ci_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.groups_inheriting_mock_ci_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active groups inheriting integrations for Mock CI
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182734_projects_mock_monitoring_active.yml b/config/metrics/counts_all/20210216182734_projects_mock_monitoring_active.yml
index c12bcfc76ae..f2addef450d 100644
--- a/config/metrics/counts_all/20210216182734_projects_mock_monitoring_active.yml
+++ b/config/metrics/counts_all/20210216182734_projects_mock_monitoring_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.projects_mock_monitoring_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of projects with active integrations for Mock Monitoring
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182736_groups_mock_monitoring_active.yml b/config/metrics/counts_all/20210216182736_groups_mock_monitoring_active.yml
index 414d3723f9b..282fd487d3d 100644
--- a/config/metrics/counts_all/20210216182736_groups_mock_monitoring_active.yml
+++ b/config/metrics/counts_all/20210216182736_groups_mock_monitoring_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.groups_mock_monitoring_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of groups with active integrations for Mock Monitoring
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182738_templates_mock_monitoring_active.yml b/config/metrics/counts_all/20210216182738_templates_mock_monitoring_active.yml
index 48397435310..e3f70b4a679 100644
--- a/config/metrics/counts_all/20210216182738_templates_mock_monitoring_active.yml
+++ b/config/metrics/counts_all/20210216182738_templates_mock_monitoring_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.templates_mock_monitoring_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active service templates for Mock Monitoring
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182739_instances_mock_monitoring_active.yml b/config/metrics/counts_all/20210216182739_instances_mock_monitoring_active.yml
index 671c0574892..fed7a283e14 100644
--- a/config/metrics/counts_all/20210216182739_instances_mock_monitoring_active.yml
+++ b/config/metrics/counts_all/20210216182739_instances_mock_monitoring_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.instances_mock_monitoring_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active instance-level integrations for Mock Monitoring
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182741_projects_inheriting_mock_monitoring_active.yml b/config/metrics/counts_all/20210216182741_projects_inheriting_mock_monitoring_active.yml
index 895e0f03505..690d27ecb63 100644
--- a/config/metrics/counts_all/20210216182741_projects_inheriting_mock_monitoring_active.yml
+++ b/config/metrics/counts_all/20210216182741_projects_inheriting_mock_monitoring_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.projects_inheriting_mock_monitoring_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active projects inheriting integrations for Mock Monitoring
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20210216182743_groups_inheriting_mock_monitoring_active.yml b/config/metrics/counts_all/20210216182743_groups_inheriting_mock_monitoring_active.yml
index 27206fb724f..a22e3be20a4 100644
--- a/config/metrics/counts_all/20210216182743_groups_inheriting_mock_monitoring_active.yml
+++ b/config/metrics/counts_all/20210216182743_groups_inheriting_mock_monitoring_active.yml
@@ -1,16 +1,19 @@
---
key_path: counts.groups_inheriting_mock_monitoring_active
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: Count of active groups inheriting integrations for Mock Monitoring
+product_section: dev
+product_stage: create
+product_group: group::ecosystem
+product_category: integrations
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.12'
time_frame: all
data_source: database
distribution:
- ce
+- ee
tier:
- free
-skip_validation: true
+- premium
+- ultimate
diff --git a/config/routes.rb b/config/routes.rb
index bc85fe440a1..07549838a94 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -44,6 +44,14 @@ Rails.application.routes.draw do
draw :oauth
use_doorkeeper_openid_connect
+ # Add OPTIONS method for CORS preflight requests
+ match '/oauth/userinfo' => 'doorkeeper/openid_connect/userinfo#show', via: :options
+ match '/oauth/discovery/keys' => 'doorkeeper/openid_connect/discovery#keys', via: :options
+ match '/.well-known/openid-configuration' => 'doorkeeper/openid_connect/discovery#provider', via: :options
+ match '/.well-known/webfinger' => 'doorkeeper/openid_connect/discovery#webfinger', via: :options
+
+ match '/oauth/token' => 'oauth/tokens#create', via: :options
+ match '/oauth/revoke' => 'oauth/tokens#revoke', via: :options
# Sign up
scope path: '/users/sign_up', module: :registrations, as: :users_sign_up do
diff --git a/db/migrate/20210602122233_add_runners_description_index.rb b/db/migrate/20210602122233_add_runners_description_index.rb
new file mode 100644
index 00000000000..ae779e62f0f
--- /dev/null
+++ b/db/migrate/20210602122233_add_runners_description_index.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddRunnersDescriptionIndex < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'index_ci_runners_on_description_trigram'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_runners, :description, name: INDEX_NAME, using: :gin, opclass: { description: :gin_trgm_ops }
+ end
+
+ def down
+ remove_concurrent_index_by_name :ci_runners, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20210602122233 b/db/schema_migrations/20210602122233
new file mode 100644
index 00000000000..c52dbdfd858
--- /dev/null
+++ b/db/schema_migrations/20210602122233
@@ -0,0 +1 @@
+96c70de2567fc3e816c720ed6e4cef2446c0f0ee288d0959cd1298523913077f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 9887ac3cdf5..5fa8419bd40 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22921,6 +22921,8 @@ CREATE INDEX index_ci_runner_projects_on_runner_id ON ci_runner_projects USING b
CREATE INDEX index_ci_runners_on_contacted_at ON ci_runners USING btree (contacted_at);
+CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops);
+
CREATE INDEX index_ci_runners_on_locked ON ci_runners USING btree (locked);
CREATE INDEX index_ci_runners_on_runner_type ON ci_runners USING btree (runner_type);
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
index a98187466e2..f6f88e9b193 100644
--- a/doc/administration/geo/disaster_recovery/index.md
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -7,17 +7,14 @@ type: howto
# Disaster Recovery (Geo) **(PREMIUM SELF)**
-Geo replicates your database, your Git repositories, and few other assets.
-We will support and replicate more data in the future, that will enable you to
-failover with minimal effort, in a disaster situation.
-
-See [Geo limitations](../index.md#limitations) for more information.
+Geo replicates your database, your Git repositories, and few other assets,
+but there are some [limitations](../index.md#limitations).
WARNING:
Disaster recovery for multi-secondary configurations is in **Alpha**.
For the latest updates, check the [Disaster Recovery epic for complete maturity](https://gitlab.com/groups/gitlab-org/-/epics/3574).
Multi-secondary configurations require the complete re-synchronization and re-configuration of all non-promoted secondaries and
-will cause downtime.
+causes downtime.
## Promoting a **secondary** Geo node in single-secondary configurations
@@ -91,7 +88,7 @@ Note the following when promoting a secondary:
before proceeding. If the secondary node
[has been paused](../../geo/index.md#pausing-and-resuming-replication), the promotion
performs a point-in-time recovery to the last known state.
- Data that was created on the primary while the secondary was paused will be lost.
+ Data that was created on the primary while the secondary was paused is lost.
- A new **secondary** should not be added at this time. If you want to add a new
**secondary**, do this after you have completed the entire process of promoting
the **secondary** to the **primary**.
@@ -497,7 +494,7 @@ must disable the **primary** site:
WARNING:
If the secondary site [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
a point-in-time recovery to the last known state.
-Data that was created on the primary while the secondary was paused will be lost.
+Data that was created on the primary while the secondary was paused is lost.
1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
promote to read-write:
@@ -513,7 +510,7 @@ Data that was created on the primary while the secondary was paused will be lost
`geo_secondary_role`:
NOTE:
- Depending on your architecture these steps will need to be run on any GitLab node that is external to the **secondary** Kubernetes cluster.
+ Depending on your architecture, these steps need to run on any GitLab node that is external to the **secondary** Kubernetes cluster.
```ruby
## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
@@ -547,7 +544,7 @@ Data that was created on the primary while the secondary was paused will be lost
helm --namespace gitlab get values gitlab-geo > gitlab.yaml
```
- The existing configuration will contain a section for Geo that should resemble:
+ The existing configuration contains a section for Geo that should resemble:
```yaml
geo:
@@ -564,7 +561,7 @@ Data that was created on the primary while the secondary was paused will be lost
To promote the **secondary** cluster to a **primary** cluster, update `role: secondary` to `role: primary`.
- You can remove the entire `psql` section if the cluster will remain as a primary site, this refers to the tracking database and will be ignored whilst the cluster is acting as a primary site.
+ If the cluster remains as a primary site, you can remove the entire `psql` section; it refers to the tracking database and is ignored whilst the cluster is acting as a primary site.
Update the cluster with the new configuration:
diff --git a/doc/api/graphql/getting_started.md b/doc/api/graphql/getting_started.md
index 1ce64f2c33d..85b36346167 100644
--- a/doc/api/graphql/getting_started.md
+++ b/doc/api/graphql/getting_started.md
@@ -4,12 +4,13 @@ group: Ecosystem
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Getting started with GitLab GraphQL API
+# Get started with GitLab GraphQL API **(FREE)**
This guide demonstrates basic usage of the GitLab GraphQL API.
-See the [GraphQL API style guide](../../development/api_graphql_styleguide.md) for implementation details
-aimed at developers who wish to work on developing the API itself.
+Read the [GraphQL API style guide](../../development/api_graphql_styleguide.md)
+for implementation details aimed at developers who wish to work on developing
+the API itself.
## Running examples
@@ -20,10 +21,11 @@ The examples documented here can be run using:
### Command line
-You can run GraphQL queries in a `curl` request on the command line on your local machine.
-A GraphQL request can be made as a `POST` request to `/api/graphql` with the query as the payload.
-You can authorize your request by generating a [personal access token](../../user/profile/personal_access_tokens.md)
-to use as a bearer token.
+You can run GraphQL queries in a `curl` request on the command line on your
+local computer. A GraphQL request can be made as a `POST` request to `/api/graphql`
+with the query as the payload. You can authorize your request by generating a
+[personal access token](../../user/profile/personal_access_tokens.md) to use as
+a bearer token.
Example:
@@ -36,24 +38,26 @@ curl "https://gitlab.com/api/graphql" --header "Authorization: Bearer $GRAPHQL_T
### GraphiQL
-GraphiQL (pronounced "graphical") allows you to run queries directly against the server endpoint
-with syntax highlighting and autocomplete. It also allows you to explore the schema and types.
+GraphiQL (pronounced "graphical") allows you to run queries directly against
+the server endpoint with syntax highlighting and autocomplete. It also allows
+you to explore the schema and types.
The examples below:
-- Can be run directly against GitLab 11.0 or later, though some of the types and fields
-may not be supported in older versions.
-- Works against GitLab.com without any further setup. Make sure you are signed in and
-navigate to the [GraphiQL Explorer](https://gitlab.com/-/graphql-explorer).
+- Can be run directly against GitLab 11.0 or later, though some of the types
+ and fields may not be supported in older versions.
+- Works against GitLab.com without any further setup. Make sure you are signed
+ in and navigate to the [GraphiQL Explorer](https://gitlab.com/-/graphql-explorer).
-If you want to run the queries locally, or on a self-managed instance,
-you must either:
+If you want to run the queries locally, or on a self-managed instance, you must
+either:
-- Create the `gitlab-org` group with a project called `graphql-sandbox` under it. Create
-several issues within the project.
-- Edit the queries to replace `gitlab-org/graphql-sandbox` with your own group and project.
+- Create the `gitlab-org` group with a project called `graphql-sandbox` under
+ it. Create several issues in the project.
+- Edit the queries to replace `gitlab-org/graphql-sandbox` with your own group
+ and project.
-Please refer to [running GraphiQL](index.md#graphiql) for more information.
+Refer to [running GraphiQL](index.md#graphiql) for more information.
NOTE:
If you are running GitLab 11.0 to 12.0, enable the `graphql`
@@ -74,8 +78,8 @@ which is an object identifier in the format of `"gid://gitlab/Issue/123"`.
[GitLab GraphQL Schema](reference/index.md) outlines which objects and fields are
available for clients to query and their corresponding data types.
-Example: Get only the names of all the projects the currently logged in user can access (up to a limit, more on that later)
-in the group `gitlab-org`.
+Example: Get only the names of all the projects the currently logged in user can
+access (up to a limit) in the group `gitlab-org`.
```graphql
query {
@@ -108,12 +112,12 @@ query {
When retrieving child nodes use:
-- the `edges { node { } }` syntax.
-- the short form `nodes { }` syntax.
+- The `edges { node { } }` syntax.
+- The short form `nodes { }` syntax.
Underneath it all is a graph we are traversing, hence the name GraphQL.
-Example: Get a project (only its name) and the titles of all its issues.
+Example: Get the name of a project, and the titles of all its issues.
```graphql
query {
@@ -130,23 +134,24 @@ query {
```
More about queries:
-[GraphQL docs](https://graphql.org/learn/queries/)
+[GraphQL documentation](https://graphql.org/learn/queries/)
### Authorization
-Authorization uses the same engine as the GitLab application (and GitLab.com). So if you've signed in to GitLab
-and use GraphiQL, all queries are performed as you, the signed in user. For more information, see the
+Authorization uses the same engine as the GitLab application (and GitLab.com).
+If you've signed in to GitLab and use GraphiQL, all queries are performed as
+you, the signed in user. For more information, read the
[GitLab API documentation](../README.md#authentication).
### Mutations
-Mutations make changes to data. We can update, delete, or create new records. Mutations
-generally use InputTypes and variables, neither of which appear here.
+Mutations make changes to data. We can update, delete, or create new records.
+Mutations generally use InputTypes and variables, neither of which appear here.
Mutations have:
- Inputs. For example, arguments, such as which emoji you'd like to award,
-and to which object.
+ and to which object.
- Return statements. That is, what you'd like to get back when it's successful.
- Errors. Always ask for what went wrong, just in case.
@@ -174,8 +179,9 @@ mutation {
}
```
-Example: Add a comment to the issue (we're using the ID of the `GitLab.com` issue - but
-if you're using a local instance, you must get the ID of an issue you can write to).
+Example: Add a comment to the issue. In this example, we use the ID of the
+`GitLab.com` issue. If you're using a local instance, you must get the ID of an
+issue you can write to.
```graphql
mutation {
@@ -196,7 +202,8 @@ mutation {
#### Update mutations
-When you see the result `id` of the note you created - take a note of it. Now let's edit it to sip faster!
+When you see the result `id` of the note you created, take a note of it. Let's
+edit it to sip faster.
```graphql
mutation {
@@ -214,7 +221,7 @@ mutation {
#### Deletion mutations
-Let's delete the comment, since our tea is all gone.
+Let's delete the comment, because our tea is all gone.
```graphql
mutation {
@@ -244,16 +251,18 @@ You should get something like the following output:
We've asked for the note details, but it doesn't exist anymore, so we get `null`.
More about mutations:
-[GraphQL Docs](https://graphql.org/learn/queries/#mutations).
+[GraphQL Documentation](https://graphql.org/learn/queries/#mutations).
### Introspective queries
Clients can query the GraphQL endpoint for information about its own schema.
by making an [introspective query](https://graphql.org/learn/introspection/).
+The [GraphiQL Query Explorer](https://gitlab.com/-/graphql-explorer) uses an
+introspection query to:
-It is through an introspection query that the [GraphiQL Query Explorer](https://gitlab.com/-/graphql-explorer)
-gets all of its knowledge about our GraphQL schema to do autocompletion and provide
-its interactive `Docs` tab.
+- Gain knowledge about our GraphQL schema.
+- Do autocompletion.
+- Provide its interactive `Docs` tab.
Example: Get all the type names in the schema.
@@ -267,8 +276,8 @@ Example: Get all the type names in the schema.
}
```
-Example: Get all the fields associated with Issue.
-`kind` tells us the enum value for the type, like `OBJECT`, `SCALAR` or `INTERFACE`.
+Example: Get all the fields associated with Issue. `kind` tells us the enum
+value for the type, like `OBJECT`, `SCALAR` or `INTERFACE`.
```graphql
query IssueTypes {
@@ -287,12 +296,12 @@ query IssueTypes {
```
More about introspection:
-[GraphQL docs](https://graphql.org/learn/introspection/)
+[GraphQL documentation](https://graphql.org/learn/introspection/)
## Sorting
-Some of the GitLab GraphQL endpoints allow you to specify how you'd like a collection of
-objects to be sorted. You can only sort by what the schema allows you to.
+Some of the GitLab GraphQL endpoints allow you to specify how to sort a
+collection of objects. You can only sort by what the schema allows you to.
Example: Issues can be sorted by creation date:
@@ -312,17 +321,18 @@ query {
## Pagination
-Pagination is a way of only asking for a subset of the records (say, the first 10).
-If we want more of them, we can make another request for the next 10 from the server
-(in the form of something like "please give me the next 10 records").
+Pagination is a way of only asking for a subset of the records, such as the
+first ten. If we want more of them, we can make another request for the next
+ten from the server in the form of something like `please give me the next ten records`.
-By default, the GitLab GraphQL API returns 100 records per page.
-This can be changed by using `first` or `last` arguments. Both arguments take a value,
-so `first: 10` returns the first 10 records, and `last: 10` the last 10 records.
-There is a limit on how many records will be returned per page, which is generally `100`.
+By default, the GitLab GraphQL API returns 100 records per page. To change this
+behavior, use `first` or `last` arguments. Both arguments take a value, so
+`first: 10` returns the first ten records, and `last: 10` the last ten records.
+There is a limit on how many records are returned per page, which is generally
+`100`.
-Example: Retrieve only the first 2 issues (slicing). The `cursor` field gives us a position from which
-we can retrieve further records relative to that one.
+Example: Retrieve only the first two issues (slicing). The `cursor` field gives
+us a position from which we can retrieve further records relative to that one.
```graphql
query {
@@ -343,9 +353,10 @@ query {
}
```
-Example: Retrieve the next 3. (The cursor value
+Example: Retrieve the next three. (The cursor value
`eyJpZCI6IjI3MDM4OTMzIiwiY3JlYXRlZF9hdCI6IjIwMTktMTEtMTQgMDU6NTY6NDQgVVRDIn0`
-could be different, but it's the `cursor` value returned for the second issue returned above.)
+could be different, but it's the `cursor` value returned for the second issue
+returned above.)
```graphql
query {
@@ -367,5 +378,5 @@ query {
}
```
-More on pagination and cursors:
-[GraphQL docs](https://graphql.org/learn/pagination/)
+More about pagination and cursors:
+[GraphQL documentation](https://graphql.org/learn/pagination/)
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index caa7429a964..621064fd29d 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -336,6 +336,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| <a id="queryrunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="queryrunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
| <a id="queryrunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
| <a id="queryrunnerstaglist"></a>`tagList` | [`[String!]`](#string) | Filter by tags associated with the runner (comma-separated or array). |
@@ -1227,6 +1228,10 @@ Input type: `CreateIssueInput`
### `Mutation.createIteration`
+WARNING:
+**Deprecated** in 14.0.
+Use iterationCreate.
+
Input type: `CreateIterationInput`
#### Arguments
@@ -1237,6 +1242,7 @@ Input type: `CreateIterationInput`
| <a id="mutationcreateiterationdescription"></a>`description` | [`String`](#string) | The description of the iteration. |
| <a id="mutationcreateiterationduedate"></a>`dueDate` | [`String`](#string) | The end date of the iteration. |
| <a id="mutationcreateiterationgrouppath"></a>`groupPath` | [`ID`](#id) | Full path of the group with which the resource is associated. |
+| <a id="mutationcreateiterationiterationscadenceid"></a>`iterationsCadenceId` | [`IterationsCadenceID`](#iterationscadenceid) | Global ID of the iterations cadence to be assigned to newly created iteration. |
| <a id="mutationcreateiterationprojectpath"></a>`projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. |
| <a id="mutationcreateiterationstartdate"></a>`startDate` | [`String`](#string) | The start date of the iteration. |
| <a id="mutationcreateiterationtitle"></a>`title` | [`String`](#string) | The title of the iteration. |
@@ -2657,6 +2663,31 @@ Input type: `IterationCadenceUpdateInput`
| <a id="mutationiterationcadenceupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationiterationcadenceupdateiterationcadence"></a>`iterationCadence` | [`IterationCadence`](#iterationcadence) | The updated iteration cadence. |
+### `Mutation.iterationCreate`
+
+Input type: `iterationCreateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationiterationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationiterationcreatedescription"></a>`description` | [`String`](#string) | The description of the iteration. |
+| <a id="mutationiterationcreateduedate"></a>`dueDate` | [`String`](#string) | The end date of the iteration. |
+| <a id="mutationiterationcreategrouppath"></a>`groupPath` | [`ID`](#id) | Full path of the group with which the resource is associated. |
+| <a id="mutationiterationcreateiterationscadenceid"></a>`iterationsCadenceId` | [`IterationsCadenceID`](#iterationscadenceid) | Global ID of the iterations cadence to be assigned to newly created iteration. |
+| <a id="mutationiterationcreateprojectpath"></a>`projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. |
+| <a id="mutationiterationcreatestartdate"></a>`startDate` | [`String`](#string) | The start date of the iteration. |
+| <a id="mutationiterationcreatetitle"></a>`title` | [`String`](#string) | The title of the iteration. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationiterationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationiterationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationiterationcreateiteration"></a>`iteration` | [`Iteration`](#iteration) | The created iteration. |
+
### `Mutation.iterationDelete`
Input type: `IterationDeleteInput`
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 54119ba64f3..c4dba255ba4 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -725,6 +725,28 @@ Example response:
}
```
+### Download a Group avatar
+
+Get a group avatar. This endpoint can be accessed without authentication if the
+group is publicly accessible.
+
+```plaintext
+GET /groups/:id/avatar
+```
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | --------------------- |
+| `id` | integer/string | yes | ID of the group |
+
+Example:
+
+```shell
+curl --header "PRIVATE-TOKEN: $GITLAB_LOCAL_TOKEN" \
+ --remote-header-name \
+ --remote-name \
+ "https://gitlab.example.com/api/v4/groups/4/avatar"
+```
+
### Disable the results limit **(FREE SELF)**
The 100 results limit can break integrations developed using GitLab 12.4 and earlier.
diff --git a/doc/api/runners.md b/doc/api/runners.md
index a6886b75a1c..951e72edcb5 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -41,6 +41,7 @@ GET /runners?scope=active
GET /runners?type=project_type
GET /runners?status=active
GET /runners?tag_list=tag1,tag2
+GET /runners?search=gitlab
```
| Attribute | Type | Required | Description |
@@ -49,6 +50,7 @@ GET /runners?tag_list=tag1,tag2
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
| `tag_list` | string array | no | List of the runner's tags |
+| `search` | string | no | The full token or partial description text to match |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 5e8fc1f8078..03efaa1a13a 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -1296,15 +1296,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.groups_datadog_active`
-Missing description
+Count of groups with active integrations for Datadog
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182549_groups_datadog_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_discord_active`
@@ -1344,15 +1344,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.groups_ewm_active`
-Missing description
+Count of groups with active integrations for EWM
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182616_groups_ewm_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_external_wiki_active`
@@ -1512,15 +1512,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_datadog_active`
-Missing description
+Count of active groups inheriting integrations for Datadog
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182557_groups_inheriting_datadog_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_discord_active`
@@ -1560,15 +1560,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_ewm_active`
-Missing description
+Count of active groups inheriting integrations for EWM
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182623_groups_inheriting_ewm_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_external_wiki_active`
@@ -1704,27 +1704,27 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_mock_ci_active`
-Missing description
+Count of active groups inheriting integrations for Mock CI
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182732_groups_inheriting_mock_ci_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_mock_monitoring_active`
-Missing description
+Count of active groups inheriting integrations for Mock Monitoring
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182743_groups_inheriting_mock_monitoring_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_inheriting_packagist_active`
@@ -1944,27 +1944,27 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.groups_mock_ci_active`
-Missing description
+Count of groups with active integrations for Mock CI
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182724_groups_mock_ci_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_mock_monitoring_active`
-Missing description
+Count of groups with active integrations for Mock Monitoring
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182736_groups_mock_monitoring_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.groups_packagist_active`
@@ -2652,15 +2652,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.instances_datadog_active`
-Missing description
+Count of active instance-level integrations for Datadog
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182553_instances_datadog_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.instances_discord_active`
@@ -2700,15 +2700,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.instances_ewm_active`
-Missing description
+Count of active instance-level integrations for EWM
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182620_instances_ewm_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.instances_external_wiki_active`
@@ -2844,27 +2844,27 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.instances_mock_ci_active`
-Missing description
+Count of active instance-level integrations for Mock CI
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182728_instances_mock_ci_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.instances_mock_monitoring_active`
-Missing description
+Count of active instance-level integrations for Mock Monitoring
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182739_instances_mock_monitoring_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.instances_packagist_active`
@@ -4308,15 +4308,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.projects_datadog_active`
-Missing description
+Count of projects with active integrations for Datadog
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182547_projects_datadog_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_discord_active`
@@ -4356,15 +4356,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.projects_ewm_active`
-Missing description
+Count of projects with active integrations for EWM
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182614_projects_ewm_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_external_wiki_active`
@@ -4536,15 +4536,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_datadog_active`
-Missing description
+Count of active projects inheriting integrations for Datadog
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182555_projects_inheriting_datadog_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_discord_active`
@@ -4584,15 +4584,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_ewm_active`
-Missing description
+Count of active projects inheriting integrations for EWM
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182622_projects_inheriting_ewm_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_external_wiki_active`
@@ -4728,27 +4728,27 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_mock_ci_active`
-Missing description
+Count of active projects inheriting integrations for Mock CI
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182730_projects_inheriting_mock_ci_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_mock_monitoring_active`
-Missing description
+Count of active projects inheriting integrations for Mock Monitoring
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182741_projects_inheriting_mock_monitoring_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_inheriting_packagist_active`
@@ -5040,27 +5040,27 @@ Tiers: `premium`, `ultimate`
### `counts.projects_mock_ci_active`
-Missing description
+Count of projects with active integrations for Mock CI
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182722_projects_mock_ci_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_mock_monitoring_active`
-Missing description
+Count of projects with active integrations for Mock Monitoring
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182734_projects_mock_monitoring_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.projects_packagist_active`
@@ -5988,15 +5988,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.templates_datadog_active`
-Missing description
+Count of active service templates for Datadog
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182551_templates_datadog_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.templates_discord_active`
@@ -6036,15 +6036,15 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.templates_ewm_active`
-Missing description
+Count of active service templates for EWM
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182618_templates_ewm_active.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.templates_external_wiki_active`
@@ -6180,27 +6180,27 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.templates_mock_ci_active`
-Missing description
+Count of active service templates for Mock CI
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182726_templates_mock_ci_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.templates_mock_monitoring_active`
-Missing description
+Count of active service templates for Mock Monitoring
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216182738_templates_mock_monitoring_active.yml)
-Group: ``
+Group: `group::ecosystem`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts.templates_packagist_active`
@@ -10440,123 +10440,123 @@ Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly`
-Missing description
+Number of users performing actions on Jira issues by month
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184957_ecosystem_total_unique_counts_monthly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.ecosystem_total_unique_counts_weekly`
-Missing description
+Number of users performing actions on Jira issues by week
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184955_ecosystem_total_unique_counts_weekly.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184955_ecosystem_total_unique_counts_weekly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers:
+Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_close_issue_monthly`
-Missing description
+Number of users closing Jira issues by month
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184941_i_ecosystem_jira_service_close_issue_monthly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_close_issue_weekly`
-Missing description
+Number of users closing Jira issues by week
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184939_i_ecosystem_jira_service_close_issue_weekly.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184939_i_ecosystem_jira_service_close_issue_weekly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers:
+Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_create_issue_monthly`
-Missing description
+Number of users creating Jira issues by month
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184953_i_ecosystem_jira_service_create_issue_monthly.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216184953_i_ecosystem_jira_service_create_issue_monthly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_create_issue_weekly`
-Missing description
+Number of users creating Jira issues by week
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184951_i_ecosystem_jira_service_create_issue_weekly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers:
+Tiers: `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_cross_reference_monthly`
-Missing description
+Number of users that cross-referenced Jira issues by month
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184945_i_ecosystem_jira_service_cross_reference_monthly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_cross_reference_weekly`
-Missing description
+Number of users that cross-referenced Jira issues by week
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184943_i_ecosystem_jira_service_cross_reference_weekly.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184943_i_ecosystem_jira_service_cross_reference_weekly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers:
+Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_list_issues_monthly`
-Missing description
+Count of Jira Issue List visits by month
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184949_i_ecosystem_jira_service_list_issues_monthly.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216184949_i_ecosystem_jira_service_list_issues_monthly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers: `free`
+Tiers: `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_jira_service_list_issues_weekly`
-Missing description
+Count of Jira Issue List visits by week
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184947_i_ecosystem_jira_service_list_issues_weekly.yml)
-Group: ``
+Group: `group::ecosystem`
Status: `data_available`
-Tiers:
+Tiers: `premium`, `ultimate`
### `redis_hll_counters.ecosystem.i_ecosystem_slack_service_confidential_issue_notification_monthly`
@@ -16260,6 +16260,18 @@ Status: `data_available`
Tiers: `premium`, `ultimate`
+### `usage_activity_by_stage.enablement.counts.geo_node_usage.git_push_event_count_weekly`
+
+Number of Git push events from Prometheus on the Geo secondary
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210604110603_git_push_event_count_weekly.yml)
+
+Group: `group::geo`
+
+Status: `data_available`
+
+Tiers: `premium`, `ultimate`
+
### `usage_activity_by_stage.enablement.geo_secondary_web_oauth_users`
Missing description
diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md
index e1b47262e16..8b14c9aa7ea 100644
--- a/doc/user/admin_area/analytics/dev_ops_report.md
+++ b/doc/user/admin_area/analytics/dev_ops_report.md
@@ -40,14 +40,17 @@ collected before this feature is available.
The DevOps Adoption tab shows you which groups within your organization are using the most essential features of GitLab:
-- Approvals
-- Code owners
-- Deployments
-- Issues
-- Merge Requests
-- Pipelines
-- Runners
-- Scans
+- Dev
+ - Issues
+ - Merge Requests
+ - Approvals
+ - Code owners
+- Sec
+ - Scans
+- Ops
+ - Runners
+ - Pipelines
+ - Deployments
Buttons to manage your groups appear in the DevOps Adoption section of the page.
@@ -57,6 +60,8 @@ DevOps Adoption allows you to:
- Identify specific groups that are lagging in their adoption of GitLab so you can help them along in their DevOps journey.
- Find the groups that have adopted certain features and can provide guidance to other groups on how to use those features.
+![DevOps Report](img/admin_devops_adoption_v14_0.png)
+
### Disable or enable DevOps Adoption
DevOps Adoption is deployed behind a feature flag that is **enabled by default**.
diff --git a/doc/user/admin_area/analytics/img/admin_devops_adoption_v14_0.png b/doc/user/admin_area/analytics/img/admin_devops_adoption_v14_0.png
new file mode 100644
index 00000000000..3e0c79fc824
--- /dev/null
+++ b/doc/user/admin_area/analytics/img/admin_devops_adoption_v14_0.png
Binary files differ
diff --git a/doc/user/group/devops_adoption/img/group_devops_adoption_v13_11.png b/doc/user/group/devops_adoption/img/group_devops_adoption_v13_11.png
deleted file mode 100644
index a6ece47ba9a..00000000000
--- a/doc/user/group/devops_adoption/img/group_devops_adoption_v13_11.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/devops_adoption/img/group_devops_adoption_v14_0.png b/doc/user/group/devops_adoption/img/group_devops_adoption_v14_0.png
new file mode 100644
index 00000000000..cd1fcfe5954
--- /dev/null
+++ b/doc/user/group/devops_adoption/img/group_devops_adoption_v14_0.png
Binary files differ
diff --git a/doc/user/group/devops_adoption/index.md b/doc/user/group/devops_adoption/index.md
index 0246ddf3493..ab10ad17ecd 100644
--- a/doc/user/group/devops_adoption/index.md
+++ b/doc/user/group/devops_adoption/index.md
@@ -24,14 +24,17 @@ To access Group DevOps Adoption, go to your group and select **Analytics > DevOp
Group DevOps Adoption shows you how individual groups and sub-groups within your organization use the following features:
-- Approvals
-- Code owners
-- Deployments
-- Issues
-- Merge Requests
-- Pipelines
-- Runners
-- Scans
+- Dev
+ - Issues
+ - Merge Requests
+ - Approvals
+ - Code owners
+- Sec
+ - Scans
+- Ops
+ - Runners
+ - Pipelines
+ - Deployments
When managing groups in the UI, you can manage your sub-groups with the **Add/Remove sub-groups**
button, in the top right hand section of your Groups pages.
@@ -42,7 +45,7 @@ With DevOps Adoption you can:
- Identify specific sub-groups that are lagging in their adoption of GitLab so you can help them along in their DevOps journey.
- Find the sub-groups that have adopted certain features and can provide guidance to other sub-groups on how to use those features.
-![DevOps Report](img/group_devops_adoption_v13_11.png)
+![DevOps Report](img/group_devops_adoption_v14_0.png)
## Enable data processing
diff --git a/lib/api/api.rb b/lib/api/api.rb
index b398f97b20c..c952ae4d982 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -173,6 +173,7 @@ module API
mount ::API::Features
mount ::API::Files
mount ::API::FreezePeriods
+ mount ::API::GroupAvatar
mount ::API::GroupBoards
mount ::API::GroupClusters
mount ::API::GroupExport
diff --git a/lib/api/group_avatar.rb b/lib/api/group_avatar.rb
new file mode 100644
index 00000000000..ddf6787f913
--- /dev/null
+++ b/lib/api/group_avatar.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module API
+ class GroupAvatar < ::API::Base
+ helpers Helpers::GroupsHelpers
+
+ feature_category :subgroups
+
+ resource :groups do
+ desc 'Download the group avatar' do
+ detail 'This feature was introduced in GitLab 14.0'
+ end
+ params do
+ requires :id, type: String, desc: 'The group id'
+ end
+ get ':id/avatar' do
+ present_carrierwave_file!(user_group.avatar)
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a8038716732..eb317813ddf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -854,9 +854,6 @@ msgstr ""
msgid "%{state} epics"
msgstr ""
-msgid "%{strongStart}Deletes%{strongEnd} source branch"
-msgstr ""
-
msgid "%{strongStart}Tip:%{strongEnd} You can also checkout merge requests locally by %{linkStart}following these guidelines%{linkEnd}"
msgstr ""
@@ -12562,9 +12559,6 @@ msgstr ""
msgid "Environments|Logs from %{start} to %{end}."
msgstr ""
-msgid "Environments|Managed apps"
-msgstr ""
-
msgid "Environments|More information"
msgstr ""
@@ -32784,6 +32778,9 @@ msgstr ""
msgid "The snippet is visible to any logged in user except external users."
msgstr ""
+msgid "The source branch will be deleted"
+msgstr ""
+
msgid "The specified tab is invalid, please select another"
msgstr ""
@@ -34369,6 +34366,9 @@ msgstr ""
msgid "Too many projects enabled. You will need to manage them via the console or the API."
msgstr ""
+msgid "TopNav|Go back"
+msgstr ""
+
msgid "Topics (optional)"
msgstr ""
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index c418ceeae61..bea0aa10c64 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -432,7 +432,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
it 'user cannot remove source branch', :sidekiq_might_not_need_inline do
expect(page).not_to have_field('remove-source-branch-input')
- expect(page).to have_content('Deletes source branch')
+ expect(page).to have_content('The source branch will be deleted')
end
end
diff --git a/spec/features/nav/top_nav_responsive_spec.rb b/spec/features/nav/top_nav_responsive_spec.rb
index 0d8c75a099d..dfe3e76f172 100644
--- a/spec/features/nav/top_nav_responsive_spec.rb
+++ b/spec/features/nav/top_nav_responsive_spec.rb
@@ -6,7 +6,6 @@ RSpec.describe 'top nav responsive', :js do
include MobileHelpers
let_it_be(:user) { create(:user) }
- let_it_be(:responsive_menu_text) { 'Placeholder for responsive top nav' }
before do
stub_feature_flags(combined_menu: true)
@@ -20,7 +19,9 @@ RSpec.describe 'top nav responsive', :js do
context 'before opened' do
it 'has page content and hides responsive menu', :aggregate_failures do
expect(page).to have_css('.page-title', text: 'Projects')
- expect(page).to have_no_text(responsive_menu_text)
+ expect(page).to have_link('Dashboard', id: 'logo')
+
+ expect(page).to have_no_css('.top-nav-responsive')
end
end
@@ -31,8 +32,22 @@ RSpec.describe 'top nav responsive', :js do
it 'hides everything and shows responsive menu', :aggregate_failures do
expect(page).to have_no_css('.page-title', text: 'Projects')
- expect(page).to have_link('Dashboard', id: 'logo')
- expect(page).to have_text(responsive_menu_text)
+ expect(page).to have_no_link('Dashboard', id: 'logo')
+
+ within '.top-nav-responsive' do
+ expect(page).to have_link(nil, href: search_path)
+ expect(page).to have_button('Projects')
+ expect(page).to have_button('Groups')
+ expect(page).to have_link('Snippets', href: dashboard_snippets_path)
+ end
+ end
+
+ it 'has new dropdown', :aggregate_failures do
+ click_button('New...')
+
+ expect(page).to have_link('New project', href: new_project_path)
+ expect(page).to have_link('New group', href: new_group_path)
+ expect(page).to have_link('New snippet', href: new_snippet_path)
end
end
end
diff --git a/spec/features/projects/environments_pod_logs_spec.rb b/spec/features/projects/environments_pod_logs_spec.rb
index eaef3e6ca28..5019e45593c 100644
--- a/spec/features/projects/environments_pod_logs_spec.rb
+++ b/spec/features/projects/environments_pod_logs_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
dropdown_items = find(".dropdown-menu").all(".dropdown-item")
expect(dropdown_items.first).to have_content(environment.name)
- expect(dropdown_items.size).to eq(3)
+ expect(dropdown_items.size).to eq(2)
end
end
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
index b40d9d7d5e2..b107708ac2c 100644
--- a/spec/frontend/logs/components/environment_logs_spec.js
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -12,7 +12,6 @@ import {
mockTrace,
mockEnvironmentsEndpoint,
mockDocumentationPath,
- mockManagedAppsEndpoint,
} from '../mock_data';
jest.mock('~/lib/utils/scroll_utils');
@@ -35,7 +34,7 @@ describe('EnvironmentLogs', () => {
environmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint,
clusterApplicationsDocumentationPath: mockDocumentationPath,
- clustersPath: mockManagedAppsEndpoint,
+ clustersPath: '/gitlab-org',
};
const updateControlBtnsMock = jest.fn();
diff --git a/spec/frontend/logs/mock_data.js b/spec/frontend/logs/mock_data.js
index 3fabab4bc59..14c8f7a2ba2 100644
--- a/spec/frontend/logs/mock_data.js
+++ b/spec/frontend/logs/mock_data.js
@@ -7,8 +7,6 @@ export const mockDocumentationPath = '/documentation.md';
export const mockLogsEndpoint = '/dummy_logs_path.json';
export const mockCursor = 'MOCK_CURSOR';
export const mockNextCursor = 'MOCK_NEXT_CURSOR';
-export const mockManagedAppName = 'kubernetes-cluster-1';
-export const mockManagedAppsEndpoint = `${mockProjectPath}/clusters.json`;
const makeMockEnvironment = (id, name, advancedQuerying) => ({
id,
@@ -25,31 +23,6 @@ export const mockEnvironments = [
makeMockEnvironment(102, 'review/a-feature', false),
];
-export const mockManagedApps = [
- {
- cluster_type: 'project_type',
- enabled: true,
- environment_scope: '*',
- name: 'kubernetes-cluster-1',
- provider_type: 'user',
- status: 'connected',
- path: '/root/autodevops-deploy/-/clusters/15',
- gitlab_managed_apps_logs_path: '/root/autodevops-deploy/-/logs?cluster_id=15',
- enable_advanced_logs_querying: true,
- },
- {
- cluster_type: 'project_type',
- enabled: true,
- environment_scope: '*',
- name: 'kubernetes-cluster-2',
- provider_type: 'user',
- status: 'connected',
- path: '/root/autodevops-deploy/-/clusters/16',
- gitlab_managed_apps_logs_path: null,
- enable_advanced_logs_querying: false,
- },
-];
-
export const mockPodName = 'production-764c58d697-aaaaa';
export const mockPods = [
mockPodName,
diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js
index d5118bbde8c..9307a3b62fb 100644
--- a/spec/frontend/logs/stores/actions_spec.js
+++ b/spec/frontend/logs/stores/actions_spec.js
@@ -11,7 +11,6 @@ import {
fetchEnvironments,
fetchLogs,
fetchMoreLogsPrepend,
- fetchManagedApps,
} from '~/logs/stores/actions';
import * as types from '~/logs/stores/mutation_types';
import logsPageState from '~/logs/stores/state';
@@ -31,8 +30,6 @@ import {
mockResponse,
mockCursor,
mockNextCursor,
- mockManagedApps,
- mockManagedAppsEndpoint,
} from '../mock_data';
jest.mock('~/flash');
@@ -219,30 +216,6 @@ describe('Logs Store actions', () => {
});
});
- describe('fetchManagedApps', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- it('should commit RECEIVE_MANAGED_APPS_DATA_SUCCESS mutation on succesful fetch', () => {
- mock.onGet(mockManagedAppsEndpoint).replyOnce(200, { clusters: mockManagedApps });
- return testAction(fetchManagedApps, mockManagedAppsEndpoint, state, [
- { type: types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, payload: mockManagedApps },
- ]);
- });
-
- it('should commit RECEIVE_MANAGED_APPS_DATA_ERROR on wrong data', () => {
- mock.onGet(mockManagedAppsEndpoint).replyOnce(500);
- return testAction(
- fetchManagedApps,
- mockManagedAppsEndpoint,
- state,
- [{ type: types.RECEIVE_MANAGED_APPS_DATA_ERROR }],
- [],
- );
- });
- });
-
describe('when the backend responds succesfully', () => {
let expectedMutations;
let expectedActions;
diff --git a/spec/frontend/logs/stores/getters_spec.js b/spec/frontend/logs/stores/getters_spec.js
index bca1ce4ca92..9d213d8c01f 100644
--- a/spec/frontend/logs/stores/getters_spec.js
+++ b/spec/frontend/logs/stores/getters_spec.js
@@ -1,14 +1,7 @@
import { trace, showAdvancedFilters } from '~/logs/stores/getters';
import logsPageState from '~/logs/stores/state';
-import {
- mockLogsResult,
- mockTrace,
- mockEnvName,
- mockEnvironments,
- mockManagedApps,
- mockManagedAppName,
-} from '../mock_data';
+import { mockLogsResult, mockTrace, mockEnvName, mockEnvironments } from '../mock_data';
describe('Logs Store getters', () => {
let state;
@@ -79,43 +72,4 @@ describe('Logs Store getters', () => {
});
});
});
-
- describe('when no managedApps are set', () => {
- beforeEach(() => {
- state.environments.current = null;
- state.environments.options = [];
- state.managedApps.current = mockManagedAppName;
- state.managedApps.options = [];
- });
-
- it('returns false', () => {
- expect(showAdvancedFilters(state)).toBe(false);
- });
- });
-
- describe('when the managedApp supports filters', () => {
- beforeEach(() => {
- state.environments.current = null;
- state.environments.options = mockEnvironments;
- state.managedApps.current = mockManagedAppName;
- state.managedApps.options = mockManagedApps;
- });
-
- it('returns true', () => {
- expect(showAdvancedFilters(state)).toBe(true);
- });
- });
-
- describe('when the managedApp does not support filters', () => {
- beforeEach(() => {
- state.environments.current = null;
- state.environments.options = mockEnvironments;
- state.managedApps.options = mockManagedApps;
- state.managedApps.current = mockManagedApps[1].name;
- });
-
- it('returns false', () => {
- expect(showAdvancedFilters(state)).toBe(false);
- });
- });
});
diff --git a/spec/frontend/logs/stores/mutations_spec.js b/spec/frontend/logs/stores/mutations_spec.js
index 111c795ba52..988197a8350 100644
--- a/spec/frontend/logs/stores/mutations_spec.js
+++ b/spec/frontend/logs/stores/mutations_spec.js
@@ -11,8 +11,6 @@ import {
mockSearch,
mockCursor,
mockNextCursor,
- mockManagedApps,
- mockManagedAppName,
} from '../mock_data';
describe('Logs Store Mutations', () => {
@@ -32,15 +30,6 @@ describe('Logs Store Mutations', () => {
it('sets the environment', () => {
mutations[types.SET_PROJECT_ENVIRONMENT](state, mockEnvName);
expect(state.environments.current).toEqual(mockEnvName);
- expect(state.managedApps.current).toBe(null);
- });
- });
-
- describe('SET_MANAGED_APP', () => {
- it('sets the managed app', () => {
- mutations[types.SET_MANAGED_APP](state, mockManagedAppName);
- expect(state.managedApps.current).toBe(mockManagedAppName);
- expect(state.environments.current).toBe(null);
});
});
@@ -265,29 +254,4 @@ describe('Logs Store Mutations', () => {
);
});
});
-
- describe('RECEIVE_MANAGED_APPS_DATA_SUCCESS', () => {
- it('receives managed apps data success', () => {
- expect(state.managedApps.options).toEqual([]);
-
- mutations[types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, mockManagedApps);
-
- expect(state.managedApps.options.length).toEqual(1);
- expect(state.managedApps.options).toEqual([mockManagedApps[0]]);
- expect(state.managedApps.isLoading).toBe(false);
- });
- });
-
- describe('RECEIVE_MANAGED_APPS_DATA_ERROR', () => {
- it('received managed apps data error', () => {
- mutations[types.RECEIVE_MANAGED_APPS_DATA_ERROR](state);
-
- expect(state.managedApps).toEqual({
- options: [],
- isLoading: false,
- current: null,
- fetchError: true,
- });
- });
- });
});
diff --git a/spec/frontend/nav/components/responsive_app_spec.js b/spec/frontend/nav/components/responsive_app_spec.js
index 1a153b38a05..3274a1b7086 100644
--- a/spec/frontend/nav/components/responsive_app_spec.js
+++ b/spec/frontend/nav/components/responsive_app_spec.js
@@ -1,7 +1,12 @@
import { shallowMount } from '@vue/test-utils';
import { range } from 'lodash';
import ResponsiveApp from '~/nav/components/responsive_app.vue';
+import ResponsiveHeader from '~/nav/components/responsive_header.vue';
+import ResponsiveHome from '~/nav/components/responsive_home.vue';
+import TopNavContainerView from '~/nav/components/top_nav_container_view.vue';
import eventHub, { EVENT_RESPONSIVE_TOGGLE } from '~/nav/event_hub';
+import { resetMenuItemsActive } from '~/nav/utils/reset_menu_items_active';
+import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
import { TEST_NAV_DATA } from '../mock_data';
describe('~/nav/components/responsive_app.vue', () => {
@@ -12,11 +17,19 @@ describe('~/nav/components/responsive_app.vue', () => {
propsData: {
navData: TEST_NAV_DATA,
},
+ stubs: {
+ KeepAliveSlots,
+ },
});
};
const triggerResponsiveToggle = () => eventHub.$emit(EVENT_RESPONSIVE_TOGGLE);
+ const findHome = () => wrapper.findComponent(ResponsiveHome);
+ const findMobileOverlay = () => wrapper.find('[data-testid="mobile-overlay"]');
+ const findSubviewHeader = () => wrapper.findComponent(ResponsiveHeader);
+ const findSubviewContainer = () => wrapper.findComponent(TopNavContainerView);
const hasBodyResponsiveOpen = () => document.body.classList.contains('top-nav-responsive-open');
+ const hasMobileOverlayVisible = () => findMobileOverlay().classes('mobile-nav-open');
beforeEach(() => {
// Add test class to reset state + assert that we're adding classes correctly
@@ -32,6 +45,13 @@ describe('~/nav/components/responsive_app.vue', () => {
createComponent();
});
+ it('shows home by default', () => {
+ expect(findHome().isVisible()).toBe(true);
+ expect(findHome().props()).toEqual({
+ navData: resetMenuItemsActive(TEST_NAV_DATA),
+ });
+ });
+
it.each`
times | expectation
${0} | ${false}
@@ -45,6 +65,78 @@ describe('~/nav/components/responsive_app.vue', () => {
expect(hasBodyResponsiveOpen()).toBe(expectation);
},
);
+
+ it.each`
+ events | expectation
+ ${[]} | ${false}
+ ${['bv::dropdown::show']} | ${true}
+ ${['bv::dropdown::show', 'bv::dropdown::hide']} | ${false}
+ `(
+ 'with root events $events, movile overlay visible = $expectation',
+ async ({ events, expectation }) => {
+ // `await...reduce(async` is like doing an `forEach(async (...))` excpet it works
+ await events.reduce(async (acc, evt) => {
+ await acc;
+
+ wrapper.vm.$root.$emit(evt);
+
+ await wrapper.vm.$nextTick();
+ }, Promise.resolve());
+
+ expect(hasMobileOverlayVisible()).toBe(expectation);
+ },
+ );
+ });
+
+ const projectsContainerProps = {
+ containerClass: 'gl-px-3',
+ frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.namespace,
+ frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.vuexModule,
+ linksPrimary: TEST_NAV_DATA.views.projects.linksPrimary,
+ linksSecondary: TEST_NAV_DATA.views.projects.linksSecondary,
+ };
+ const groupsContainerProps = {
+ containerClass: 'gl-px-3',
+ frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_GROUPS.namespace,
+ frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_GROUPS.vuexModule,
+ linksPrimary: TEST_NAV_DATA.views.groups.linksPrimary,
+ linksSecondary: TEST_NAV_DATA.views.groups.linksSecondary,
+ };
+
+ describe.each`
+ view | header | containerProps
+ ${'projects'} | ${'Projects'} | ${projectsContainerProps}
+ ${'groups'} | ${'Groups'} | ${groupsContainerProps}
+ `('when menu item with $view is clicked', ({ view, header, containerProps }) => {
+ beforeEach(async () => {
+ createComponent();
+
+ findHome().vm.$emit('menu-item-click', { view });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('shows header', () => {
+ expect(findSubviewHeader().text()).toBe(header);
+ });
+
+ it('shows container subview', () => {
+ expect(findSubviewContainer().props()).toEqual(containerProps);
+ });
+
+ it('hides home', () => {
+ expect(findHome().isVisible()).toBe(false);
+ });
+
+ describe('when header back button is clicked', () => {
+ beforeEach(() => {
+ findSubviewHeader().vm.$emit('menu-item-click', { view: 'home' });
+ });
+
+ it('shows home', () => {
+ expect(findHome().isVisible()).toBe(true);
+ });
+ });
});
describe('when destroyed', () => {
diff --git a/spec/frontend/nav/components/responsive_header_spec.js b/spec/frontend/nav/components/responsive_header_spec.js
new file mode 100644
index 00000000000..937c44727c7
--- /dev/null
+++ b/spec/frontend/nav/components/responsive_header_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import ResponsiveHeader from '~/nav/components/responsive_header.vue';
+import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue';
+
+const TEST_SLOT_CONTENT = 'Test slot content';
+
+describe('~/nav/components/top_nav_menu_sections.vue', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(ResponsiveHeader, {
+ slots: {
+ default: TEST_SLOT_CONTENT,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ const findMenuItem = () => wrapper.findComponent(TopNavMenuItem);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders slot', () => {
+ expect(wrapper.text()).toBe(TEST_SLOT_CONTENT);
+ });
+
+ it('renders back button', () => {
+ const button = findMenuItem();
+
+ const tooltip = getBinding(button.element, 'gl-tooltip').value.title;
+
+ expect(tooltip).toBe('Go back');
+ expect(button.props()).toEqual({
+ menuItem: {
+ id: 'home',
+ view: 'home',
+ icon: 'angle-left',
+ },
+ iconOnly: true,
+ });
+ });
+
+ it('emits nothing', () => {
+ expect(wrapper.emitted()).toEqual({});
+ });
+
+ describe('when back button is clicked', () => {
+ beforeEach(() => {
+ findMenuItem().vm.$emit('click');
+ });
+
+ it('emits menu-item-click', () => {
+ expect(wrapper.emitted()).toEqual({
+ 'menu-item-click': [[{ id: 'home', view: 'home', icon: 'angle-left' }]],
+ });
+ });
+ });
+});
diff --git a/spec/frontend/nav/components/responsive_home_spec.js b/spec/frontend/nav/components/responsive_home_spec.js
new file mode 100644
index 00000000000..8f198d92747
--- /dev/null
+++ b/spec/frontend/nav/components/responsive_home_spec.js
@@ -0,0 +1,137 @@
+import { shallowMount } from '@vue/test-utils';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import ResponsiveHome from '~/nav/components/responsive_home.vue';
+import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue';
+import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue';
+import TopNavNewDropdown from '~/nav/components/top_nav_new_dropdown.vue';
+import { TEST_NAV_DATA } from '../mock_data';
+
+const TEST_SEARCH_MENU_ITEM = {
+ id: 'search',
+ title: 'search',
+ icon: 'search',
+ href: '/search',
+};
+
+const TEST_NEW_DROPDOWN_VIEW_MODEL = {
+ title: 'new',
+ menu_sections: [],
+};
+
+describe('~/nav/components/responsive_home.vue', () => {
+ let wrapper;
+ let menuItemClickListener;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ResponsiveHome, {
+ propsData: {
+ navData: TEST_NAV_DATA,
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ listeners: {
+ 'menu-item-click': menuItemClickListener,
+ },
+ });
+ };
+
+ const findSearchMenuItem = () => wrapper.findComponent(TopNavMenuItem);
+ const findNewDropdown = () => wrapper.findComponent(TopNavNewDropdown);
+ const findMenuSections = () => wrapper.findComponent(TopNavMenuSections);
+
+ beforeEach(() => {
+ menuItemClickListener = jest.fn();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each`
+ desc | fn
+ ${'does not show search menu item'} | ${findSearchMenuItem}
+ ${'does not show new dropdown'} | ${findNewDropdown}
+ `('$desc', ({ fn }) => {
+ expect(fn().exists()).toBe(false);
+ });
+
+ it('shows menu sections', () => {
+ expect(findMenuSections().props('sections')).toEqual([
+ { id: 'primary', menuItems: TEST_NAV_DATA.primary },
+ { id: 'secondary', menuItems: TEST_NAV_DATA.secondary },
+ ]);
+ });
+
+ it('emits when menu sections emits', () => {
+ expect(menuItemClickListener).not.toHaveBeenCalled();
+
+ findMenuSections().vm.$emit('menu-item-click', TEST_NAV_DATA.primary[0]);
+
+ expect(menuItemClickListener).toHaveBeenCalledWith(TEST_NAV_DATA.primary[0]);
+ });
+ });
+
+ describe('without secondary', () => {
+ beforeEach(() => {
+ createComponent({ navData: { ...TEST_NAV_DATA, secondary: null } });
+ });
+
+ it('shows menu sections', () => {
+ expect(findMenuSections().props('sections')).toEqual([
+ { id: 'primary', menuItems: TEST_NAV_DATA.primary },
+ ]);
+ });
+ });
+
+ describe('with search view', () => {
+ beforeEach(() => {
+ createComponent({
+ navData: {
+ ...TEST_NAV_DATA,
+ views: { search: TEST_SEARCH_MENU_ITEM },
+ },
+ });
+ });
+
+ it('shows search menu item', () => {
+ expect(findSearchMenuItem().props()).toEqual({
+ menuItem: TEST_SEARCH_MENU_ITEM,
+ iconOnly: true,
+ });
+ });
+
+ it('shows tooltip for search', () => {
+ const tooltip = getBinding(findSearchMenuItem().element, 'gl-tooltip');
+ expect(tooltip.value).toEqual({ title: TEST_SEARCH_MENU_ITEM.title });
+ });
+ });
+
+ describe('with new view', () => {
+ beforeEach(() => {
+ createComponent({
+ navData: {
+ ...TEST_NAV_DATA,
+ views: { new: TEST_NEW_DROPDOWN_VIEW_MODEL },
+ },
+ });
+ });
+
+ it('shows new dropdown', () => {
+ expect(findNewDropdown().props()).toEqual({
+ viewModel: TEST_NEW_DROPDOWN_VIEW_MODEL,
+ });
+ });
+
+ it('shows tooltip for new dropdown', () => {
+ const tooltip = getBinding(findNewDropdown().element, 'gl-tooltip');
+ expect(tooltip.value).toEqual({ title: TEST_NEW_DROPDOWN_VIEW_MODEL.title });
+ });
+ });
+});
diff --git a/spec/frontend/nav/components/top_nav_container_view_spec.js b/spec/frontend/nav/components/top_nav_container_view_spec.js
index 45c243191d8..06d2179b859 100644
--- a/spec/frontend/nav/components/top_nav_container_view_spec.js
+++ b/spec/frontend/nav/components/top_nav_container_view_spec.js
@@ -13,6 +13,7 @@ const DEFAULT_PROPS = {
frequentItemsVuexModule: FREQUENT_ITEMS_PROJECTS.vuexModule,
linksPrimary: TEST_NAV_DATA.primary,
linksSecondary: TEST_NAV_DATA.secondary,
+ containerClass: 'test-frequent-items-container-class',
};
const TEST_OTHER_PROPS = {
namespace: 'projects',
@@ -44,6 +45,7 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
attributes: parent.findComponent(FrequentItemsApp).attributes(),
};
};
+ const findFrequentItemsContainer = () => wrapper.find('[data-testid="frequent-items-container"]');
afterEach(() => {
wrapper.destroy();
@@ -85,6 +87,10 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
});
+ it('renders given container class', () => {
+ expect(findFrequentItemsContainer().classes(DEFAULT_PROPS.containerClass)).toBe(true);
+ });
+
it('renders menu sections', () => {
const sections = [
{ id: 'primary', menuItems: TEST_NAV_DATA.primary },
diff --git a/spec/frontend/nav/components/top_nav_menu_item_spec.js b/spec/frontend/nav/components/top_nav_menu_item_spec.js
index 37b58412389..fd2b4d3b056 100644
--- a/spec/frontend/nav/components/top_nav_menu_item_spec.js
+++ b/spec/frontend/nav/components/top_nav_menu_item_spec.js
@@ -30,7 +30,10 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
const findButtonIcons = () =>
findButton()
.findAllComponents(GlIcon)
- .wrappers.map((x) => x.props('name'));
+ .wrappers.map((x) => ({
+ name: x.props('name'),
+ classes: x.classes(),
+ }));
beforeEach(() => {
listener = jest.fn();
@@ -65,11 +68,42 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
expect(listener).toHaveBeenCalledWith('TEST');
});
+
+ it('renders expected icons', () => {
+ expect(findButtonIcons()).toEqual([
+ {
+ name: TEST_MENU_ITEM.icon,
+ classes: ['gl-mr-2!'],
+ },
+ {
+ name: 'chevron-right',
+ classes: ['gl-ml-auto'],
+ },
+ ]);
+ });
+ });
+
+ describe('with icon-only', () => {
+ beforeEach(() => {
+ createComponent({ iconOnly: true });
+ });
+
+ it('does not render title or view icon', () => {
+ expect(wrapper.text()).toBe('');
+ });
+
+ it('only renders menuItem icon', () => {
+ expect(findButtonIcons()).toEqual([
+ {
+ name: TEST_MENU_ITEM.icon,
+ classes: [],
+ },
+ ]);
+ });
});
describe.each`
desc | menuItem | expectedIcons
- ${'default'} | ${TEST_MENU_ITEM} | ${[TEST_MENU_ITEM.icon, 'chevron-right']}
${'with no icon'} | ${{ ...TEST_MENU_ITEM, icon: null }} | ${['chevron-right']}
${'with no view'} | ${{ ...TEST_MENU_ITEM, view: null }} | ${[TEST_MENU_ITEM.icon]}
${'with no icon or view'} | ${{ ...TEST_MENU_ITEM, view: null, icon: null }} | ${[]}
@@ -79,7 +113,7 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
});
it(`renders expected icons ${JSON.stringify(expectedIcons)}`, () => {
- expect(findButtonIcons()).toEqual(expectedIcons);
+ expect(findButtonIcons().map((x) => x.name)).toEqual(expectedIcons);
});
});
diff --git a/spec/frontend/nav/components/top_nav_menu_sections_spec.js b/spec/frontend/nav/components/top_nav_menu_sections_spec.js
index a6739507915..d56542fe572 100644
--- a/spec/frontend/nav/components/top_nav_menu_sections_spec.js
+++ b/spec/frontend/nav/components/top_nav_menu_sections_spec.js
@@ -51,11 +51,11 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
menuItems: [
{
menuItem: TEST_SECTIONS[0].menuItems[0],
- classes: [],
+ classes: ['gl-w-full'],
},
...TEST_SECTIONS[0].menuItems.slice(1).map((menuItem) => ({
menuItem,
- classes: ['gl-mt-1'],
+ classes: ['gl-w-full', 'gl-mt-1'],
})),
],
},
@@ -64,11 +64,11 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
menuItems: [
{
menuItem: TEST_SECTIONS[1].menuItems[0],
- classes: [],
+ classes: ['gl-w-full'],
},
...TEST_SECTIONS[1].menuItems.slice(1).map((menuItem) => ({
menuItem,
- classes: ['gl-mt-1'],
+ classes: ['gl-w-full', 'gl-mt-1'],
})),
],
},
diff --git a/spec/frontend/nav/components/top_nav_new_dropdown_spec.js b/spec/frontend/nav/components/top_nav_new_dropdown_spec.js
new file mode 100644
index 00000000000..18210658b89
--- /dev/null
+++ b/spec/frontend/nav/components/top_nav_new_dropdown_spec.js
@@ -0,0 +1,122 @@
+import { GlDropdown } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TopNavNewDropdown from '~/nav/components/top_nav_new_dropdown.vue';
+
+const TEST_VIEW_MODEL = {
+ title: 'Dropdown',
+ menu_sections: [
+ {
+ title: 'Section 1',
+ menu_items: [
+ { id: 'foo-1', title: 'Foo 1', href: '/foo/1' },
+ { id: 'foo-2', title: 'Foo 2', href: '/foo/2' },
+ { id: 'foo-3', title: 'Foo 3', href: '/foo/3' },
+ ],
+ },
+ {
+ title: 'Section 2',
+ menu_items: [
+ { id: 'bar-1', title: 'Bar 1', href: '/bar/1' },
+ { id: 'bar-2', title: 'Bar 2', href: '/bar/2' },
+ ],
+ },
+ ],
+};
+
+describe('~/nav/components/top_nav_menu_sections.vue', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(TopNavNewDropdown, {
+ propsData: {
+ viewModel: TEST_VIEW_MODEL,
+ ...props,
+ },
+ });
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownContents = () =>
+ findDropdown()
+ .findAll('[data-testid]')
+ .wrappers.map((child) => {
+ const type = child.attributes('data-testid');
+
+ if (type === 'divider') {
+ return { type };
+ } else if (type === 'header') {
+ return { type, text: child.text() };
+ }
+
+ return {
+ type,
+ text: child.text(),
+ href: child.attributes('href'),
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders dropdown parent', () => {
+ expect(findDropdown().props()).toMatchObject({
+ text: TEST_VIEW_MODEL.title,
+ textSrOnly: true,
+ icon: 'plus',
+ });
+ });
+
+ it('renders dropdown content', () => {
+ expect(findDropdownContents()).toEqual([
+ {
+ type: 'header',
+ text: TEST_VIEW_MODEL.menu_sections[0].title,
+ },
+ ...TEST_VIEW_MODEL.menu_sections[0].menu_items.map(({ title, href }) => ({
+ type: 'item',
+ href,
+ text: title,
+ })),
+ {
+ type: 'divider',
+ },
+ {
+ type: 'header',
+ text: TEST_VIEW_MODEL.menu_sections[1].title,
+ },
+ ...TEST_VIEW_MODEL.menu_sections[1].menu_items.map(({ title, href }) => ({
+ type: 'item',
+ href,
+ text: title,
+ })),
+ ]);
+ });
+ });
+
+ describe('with only 1 section', () => {
+ beforeEach(() => {
+ createComponent({
+ viewModel: {
+ ...TEST_VIEW_MODEL,
+ menu_sections: TEST_VIEW_MODEL.menu_sections.slice(0, 1),
+ },
+ });
+ });
+
+ it('renders dropdown content without headers and dividers', () => {
+ expect(findDropdownContents()).toEqual(
+ TEST_VIEW_MODEL.menu_sections[0].menu_items.map(({ title, href }) => ({
+ type: 'item',
+ href,
+ text: title,
+ })),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/nav/mock_data.js b/spec/frontend/nav/mock_data.js
index 2987d8deb16..c2ad86a4605 100644
--- a/spec/frontend/nav/mock_data.js
+++ b/spec/frontend/nav/mock_data.js
@@ -25,11 +25,15 @@ export const TEST_NAV_DATA = {
namespace: 'projects',
currentUserName: '',
currentItem: {},
+ linksPrimary: [{ id: 'project-link', href: '/path/to/projects', title: 'Project Link' }],
+ linksSecondary: [],
},
groups: {
namespace: 'groups',
currentUserName: '',
currentItem: {},
+ linksPrimary: [],
+ linksSecondary: [{ id: 'group-link', href: '/path/to/groups', title: 'Group Link' }],
},
},
};
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index 2cc8b2d389d..9da370747fc 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -532,7 +532,7 @@ describe('MrWidgetOptions', () => {
nextTick(() => {
const tooltip = wrapper.find('[data-testid="question-o-icon"]');
- expect(wrapper.text()).toContain('Deletes source branch');
+ expect(wrapper.text()).toContain('The source branch will be deleted');
expect(tooltip.attributes('title')).toBe(
'A user with write access to the source branch selected this option',
);
@@ -548,7 +548,7 @@ describe('MrWidgetOptions', () => {
nextTick(() => {
expect(wrapper.text()).toContain('The source branch has been deleted');
- expect(wrapper.text()).not.toContain('Deletes source branch');
+ expect(wrapper.text()).not.toContain('The source branch will be deleted');
done();
});
diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
index 006d6785506..d89beb183f4 100644
--- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
@@ -10,15 +10,15 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:inactive_project_runner) do
- create(:ci_runner, :project, projects: [project], active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
+ create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
end
let_it_be(:offline_project_runner) do
- create(:ci_runner, :project, projects: [project], contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
+ create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
end
- let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], contacted_at: 1.second.ago) }
- let_it_be(:instance_runner) { create(:ci_runner, :instance, contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 1.second.ago) }
+ let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
describe '#resolve' do
subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a }
@@ -27,6 +27,14 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
{}
end
+ context 'when the user cannot see runners' do
+ let(:user) { create(:user) }
+
+ it 'returns no runners' do
+ is_expected.to be_empty
+ end
+ end
+
context 'without sort' do
it 'returns all the runners' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner)
@@ -132,5 +140,35 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
end
end
end
+
+ context 'when text is filtered' do
+ let(:args) do
+ { search: search_term }
+ end
+
+ context 'to "project"' do
+ let(:search_term) { 'project' }
+
+ it 'returns both project runners' do
+ is_expected.to contain_exactly(inactive_project_runner, offline_project_runner)
+ end
+ end
+
+ context 'to "group"' do
+ let(:search_term) { 'group' }
+
+ it 'returns group runner' do
+ is_expected.to contain_exactly(group_runner)
+ end
+ end
+
+ context 'to "defghi"' do
+ let(:search_term) { 'defghi' }
+
+ it 'returns runners containing term in token' do
+ is_expected.to contain_exactly(offline_project_runner)
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/nav/top_nav_helper_spec.rb b/spec/helpers/nav/top_nav_helper_spec.rb
index 9426f39cd80..b9ab6f9d8fd 100644
--- a/spec/helpers/nav/top_nav_helper_spec.rb
+++ b/spec/helpers/nav/top_nav_helper_spec.rb
@@ -5,11 +5,16 @@ require 'spec_helper'
RSpec.describe Nav::TopNavHelper do
include ActionView::Helpers::UrlHelper
- describe '#top_nav_view_model' do
- let_it_be(:user) { build_stubbed(:user) }
- let_it_be(:admin) { build_stubbed(:user, :admin) }
+ let_it_be(:user) { build_stubbed(:user) }
+ let_it_be(:admin) { build_stubbed(:user, :admin) }
+
+ let(:current_user) { nil }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ end
- let(:current_user) { nil }
+ describe '#top_nav_view_model' do
let(:current_project) { nil }
let(:current_group) { nil }
let(:with_current_settings_admin_mode) { false }
@@ -26,7 +31,6 @@ RSpec.describe Nav::TopNavHelper do
let(:active_title) { 'Menu' }
before do
- allow(helper).to receive(:current_user) { current_user }
allow(Gitlab::CurrentSettings).to receive(:admin_mode) { with_current_settings_admin_mode }
allow(helper).to receive(:header_link?).with(:admin_mode) { with_header_link_admin_mode }
allow(Gitlab::Sherlock).to receive(:enabled?) { with_sherlock_enabled }
@@ -487,4 +491,50 @@ RSpec.describe Nav::TopNavHelper do
end
end
end
+
+ describe '#top_nav_responsive_view_model' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+
+ let(:with_search) { false }
+ let(:with_new_view_model) { nil }
+
+ let(:subject) { helper.top_nav_responsive_view_model(project: project, group: group) }
+
+ before do
+ allow(helper).to receive(:header_link?).with(:search) { with_search }
+ allow(helper).to receive(:new_dropdown_view_model).with(project: project, group: group) { with_new_view_model }
+ end
+
+ it 'has nil new subview' do
+ expect(subject[:views][:new]).to be_nil
+ end
+
+ it 'has nil search subview' do
+ expect(subject[:views][:search]).to be_nil
+ end
+
+ context 'with search' do
+ let(:with_search) { true }
+
+ it 'has search subview' do
+ expect(subject[:views][:search]).to eq(
+ ::Gitlab::Nav::TopNavMenuItem.build(
+ id: 'search',
+ title: 'Search',
+ icon: 'search',
+ href: search_path
+ )
+ )
+ end
+ end
+
+ context 'with new' do
+ let(:with_new_view_model) { { id: 'test-new-view-model' } }
+
+ it 'has new subview' do
+ expect(subject[:views][:new]).to eq({ id: 'test-new-view-model' })
+ end
+ end
+ end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index ab7076e037e..61f80bd43b1 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -873,12 +873,12 @@ RSpec.describe Ci::Runner do
expect(described_class.search(runner.token)).to eq([runner])
end
- it 'returns runners with a partially matching token' do
- expect(described_class.search(runner.token[0..2])).to eq([runner])
+ it 'does not return runners with a partially matching token' do
+ expect(described_class.search(runner.token[0..2])).to be_empty
end
- it 'returns runners with a matching token regardless of the casing' do
- expect(described_class.search(runner.token.upcase)).to eq([runner])
+ it 'does not return runners with a matching token with different casing' do
+ expect(described_class.search(runner.token.upcase)).to be_empty
end
it 'returns runners with a matching description' do
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index b8bf4f9683c..6e9d02b157b 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -757,19 +757,8 @@ RSpec.describe Packages::Package, type: :model do
end
end
- context 'with arel scope feature flag enabled' do
- it_behaves_like 'order_project_path scope'
- it_behaves_like 'order_project_path_desc scope'
- end
-
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(arel_package_scopes: false)
- end
-
- it_behaves_like 'order_project_path scope'
- it_behaves_like 'order_project_path_desc scope'
- end
+ it_behaves_like 'order_project_path scope'
+ it_behaves_like 'order_project_path_desc scope'
end
describe '.order_by_package_file' do
diff --git a/spec/requests/api/group_avatar_spec.rb b/spec/requests/api/group_avatar_spec.rb
new file mode 100644
index 00000000000..be5cfbc234c
--- /dev/null
+++ b/spec/requests/api/group_avatar_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GroupAvatar do
+ def avatar_path(group)
+ "/groups/#{group.id}/avatar"
+ end
+
+ describe 'GET /groups/:id/avatar' do
+ context 'when the group is public' do
+ it 'retrieves the avatar successfully' do
+ group = create(:group, :public, :with_avatar)
+
+ get api(avatar_path(group))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'when the group does not have avatar' do
+ it 'returns :not_found' do
+ group = create(:group, :public)
+
+ get api(avatar_path(group))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'when the group is private' do
+ let(:group) { create(:group, :private, :with_avatar) }
+
+ context 'when the user is not authenticated' do
+ it 'returns :not_found' do
+ get api(avatar_path(group))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the the group user is authenticated' do
+ context 'and have access to the group' do
+ it 'retrieves the avatar successfully' do
+ owner = create(:user)
+ group.add_owner(owner)
+
+ get api(avatar_path(group), owner)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'and does not have access to the group' do
+ it 'returns :not_found' do
+ get api(avatar_path(group), create(:user))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/oauth/tokens_controller_spec.rb b/spec/requests/oauth/tokens_controller_spec.rb
index c3cdae2cd21..1967d0ba8b1 100644
--- a/spec/requests/oauth/tokens_controller_spec.rb
+++ b/spec/requests/oauth/tokens_controller_spec.rb
@@ -3,12 +3,71 @@
require 'spec_helper'
RSpec.describe Oauth::TokensController do
- it 'allows cross-origin POST requests' do
- post '/oauth/token', headers: { 'Origin' => 'http://notgitlab.com' }
+ let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } }
+ let(:other_headers) { {} }
+ let(:headers) { cors_request_headers.merge(other_headers)}
- expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
- expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST'
- expect(response.headers['Access-Control-Allow-Headers']).to be_nil
- expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
+ shared_examples 'cross-origin POST request' do
+ it 'allows cross-origin requests' do
+ expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST'
+ expect(response.headers['Access-Control-Allow-Headers']).to be_nil
+ expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
+ end
+ end
+
+ shared_examples 'CORS preflight OPTIONS request' do
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'allows cross-origin requests' do
+ expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST'
+ expect(response.headers['Access-Control-Allow-Headers']).to eq 'Authorization'
+ expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
+ end
+ end
+
+ describe 'POST /oauth/token' do
+ before do
+ post '/oauth/token', headers: headers
+ end
+
+ it_behaves_like 'cross-origin POST request'
+ end
+
+ describe 'OPTIONS /oauth/token' do
+ let(:other_headers) { { 'Access-Control-Request-Headers' => 'Authorization', 'Access-Control-Request-Method' => 'POST' } }
+
+ before do
+ options '/oauth/token', headers: headers
+ end
+
+ it_behaves_like 'CORS preflight OPTIONS request'
+ end
+
+ describe 'POST /oauth/revoke' do
+ let(:other_headers) { { 'Content-Type' => 'application/x-www-form-urlencoded' } }
+
+ before do
+ post '/oauth/revoke', headers: headers, params: { token: '12345' }
+ end
+
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it_behaves_like 'cross-origin POST request'
+ end
+
+ describe 'OPTIONS /oauth/revoke' do
+ let(:other_headers) { { 'Access-Control-Request-Headers' => 'Authorization', 'Access-Control-Request-Method' => 'POST' } }
+
+ before do
+ options '/oauth/revoke', headers: headers
+ end
+
+ it_behaves_like 'CORS preflight OPTIONS request'
end
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 7b682d76150..5bf786f2290 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -41,6 +41,8 @@ RSpec.describe 'OpenID Connect requests' do
}
end
+ let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } }
+
def request_access_token!
login_as user
@@ -81,6 +83,24 @@ RSpec.describe 'OpenID Connect requests' do
end
end
+ shared_examples 'cross-origin GET request' do
+ it 'allows cross-origin request' do
+ expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, HEAD'
+ expect(response.headers['Access-Control-Allow-Headers']).to be_nil
+ expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
+ end
+ end
+
+ shared_examples 'cross-origin GET and POST request' do
+ it 'allows cross-origin request' do
+ expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, HEAD, POST'
+ expect(response.headers['Access-Control-Allow-Headers']).to be_nil
+ expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
+ end
+ end
+
context 'Application with OpenID scope' do
let(:application) { create :oauth_application, scopes: 'openid' }
@@ -180,6 +200,51 @@ RSpec.describe 'OpenID Connect requests' do
expect(response).to redirect_to('/users/sign_in')
end
end
+
+ context 'OpenID Discovery keys' do
+ context 'with a cross-origin request' do
+ before do
+ get '/oauth/discovery/keys', headers: cors_request_headers
+ end
+
+ it 'returns data' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it_behaves_like 'cross-origin GET request'
+ end
+
+ context 'with a cross-origin preflight OPTIONS request' do
+ before do
+ options '/oauth/discovery/keys', headers: cors_request_headers
+ end
+
+ it_behaves_like 'cross-origin GET request'
+ end
+ end
+
+ context 'OpenID WebFinger endpoint' do
+ context 'with a cross-origin request' do
+ before do
+ get '/.well-known/webfinger', headers: cors_request_headers, params: { resource: 'user@example.com' }
+ end
+
+ it 'returns data' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['subject']).to eq('user@example.com')
+ end
+
+ it_behaves_like 'cross-origin GET request'
+ end
+ end
+
+ context 'with a cross-origin preflight OPTIONS request' do
+ before do
+ options '/.well-known/webfinger', headers: cors_request_headers, params: { resource: 'user@example.com' }
+ end
+
+ it_behaves_like 'cross-origin GET request'
+ end
end
context 'OpenID configuration information' do
@@ -191,6 +256,27 @@ RSpec.describe 'OpenID Connect requests' do
expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
expect(json_response['scopes_supported']).to eq(%w[api read_user read_api read_repository write_repository sudo openid profile email])
end
+
+ context 'with a cross-origin request' do
+ before do
+ get '/.well-known/openid-configuration', headers: cors_request_headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['issuer']).to eq('http://localhost')
+ expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
+ expect(json_response['scopes_supported']).to eq(%w[api read_user read_api read_repository write_repository sudo openid profile email])
+ end
+
+ it_behaves_like 'cross-origin GET request'
+ end
+
+ context 'with a cross-origin preflight OPTIONS request' do
+ before do
+ options '/.well-known/openid-configuration', headers: cors_request_headers
+ end
+
+ it_behaves_like 'cross-origin GET request'
+ end
end
context 'Application with OpenID and email scopes' do
@@ -218,6 +304,30 @@ RSpec.describe 'OpenID Connect requests' do
it 'has true in email_verified claim' do
expect(json_response['email_verified']).to eq(true)
end
+
+ context 'with a cross-origin request' do
+ before do
+ get '/oauth/userinfo', headers: cors_request_headers
+ end
+
+ it_behaves_like 'cross-origin GET and POST request'
+ end
+
+ context 'with a cross-origin POST request' do
+ before do
+ post '/oauth/userinfo', headers: cors_request_headers
+ end
+
+ it_behaves_like 'cross-origin GET and POST request'
+ end
+
+ context 'with a cross-origin preflight OPTIONS request' do
+ before do
+ options '/oauth/userinfo', headers: cors_request_headers
+ end
+
+ it_behaves_like 'cross-origin GET and POST request'
+ end
end
context 'ID token payload' do
diff --git a/vendor/project_templates/cluster_management.tar.gz b/vendor/project_templates/cluster_management.tar.gz
index 598b96d0308..f4afa928d1c 100644
--- a/vendor/project_templates/cluster_management.tar.gz
+++ b/vendor/project_templates/cluster_management.tar.gz
Binary files differ