diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-09 15:10:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-09 15:10:05 +0000 |
commit | c0f42c6d662b776777afbf79ba72d8e833b8de48 (patch) | |
tree | d94d38bccd5297f59522090fd3c814d9264a1cc9 | |
parent | f4d6d3ec77286fa64810bd6a25c58671e0deedaf (diff) | |
download | gitlab-ce-c0f42c6d662b776777afbf79ba72d8e833b8de48.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 @@ -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 Binary files differnew file mode 100644 index 00000000000..3e0c79fc824 --- /dev/null +++ b/doc/user/admin_area/analytics/img/admin_devops_adoption_v14_0.png 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 Binary files differdeleted file mode 100644 index a6ece47ba9a..00000000000 --- a/doc/user/group/devops_adoption/img/group_devops_adoption_v13_11.png +++ /dev/null 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 Binary files differnew file mode 100644 index 00000000000..cd1fcfe5954 --- /dev/null +++ b/doc/user/group/devops_adoption/img/group_devops_adoption_v14_0.png 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 Binary files differindex 598b96d0308..f4afa928d1c 100644 --- a/vendor/project_templates/cluster_management.tar.gz +++ b/vendor/project_templates/cluster_management.tar.gz |