Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-08-02 21:12:40 +00:00
parent 48902b7e07
commit 68d3938909
66 changed files with 993 additions and 702 deletions

View File

@ -41,7 +41,7 @@ review-docs-cleanup:
.docs-markdown-lint-image:
# When updating the image version here, update it in /scripts/lint-doc.sh too.
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.20-vale-3.4.2-markdownlint2-0.13.0-lychee-0.15.1
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.20-vale-3.6.1-markdownlint2-0.13.0-lychee-0.15.1
docs-lint markdown:
extends:

View File

@ -4317,7 +4317,6 @@ RSpec/FeatureCategory:
- 'spec/views/dashboard/milestones/index.html.haml_spec.rb'
- 'spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb'
- 'spec/views/dashboard/projects/_blank_state_welcome.html.haml_spec.rb'
- 'spec/views/dashboard/projects/_nav.html.haml_spec.rb'
- 'spec/views/devise/confirmations/almost_there.html.haml_spec.rb'
- 'spec/views/errors/access_denied.html.haml_spec.rb'
- 'spec/views/errors/omniauth_error.html.haml_spec.rb'

View File

@ -0,0 +1,60 @@
<script>
import { GlIcon, GlButton } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { s__ } from '~/locale';
import LockTooltip from './lock_tooltip.vue';
export default {
name: 'CascadingLockIcon',
i18n: {
lockIconLabel: s__('CascadingSettings|Lock tooltip icon'),
},
components: {
GlIcon,
GlButton,
LockTooltip,
},
props: {
ancestorNamespace: {
type: Object,
required: false,
default: null,
validator: (value) => value?.path && value?.fullName,
},
isLockedByApplicationSettings: {
type: Boolean,
required: true,
},
isLockedByGroupAncestor: {
type: Boolean,
required: true,
},
},
data() {
return {
targetElement: null,
};
},
async mounted() {
// Wait until all children components are mounted
await this.$nextTick();
this.targetElement = this.$refs[this.$options.refName].$el;
},
refName: uniqueId('cascading-lock-icon-'),
};
</script>
<template>
<span>
<gl-button :ref="$options.refName" class="gl-hover-bg-transparent! !gl-p-0" category="tertiary">
<gl-icon name="lock" :aria-label="$options.i18n.lockIconLabel" class="!gl-text-gray-400" />
</gl-button>
<lock-tooltip
v-if="targetElement"
:ancestor-namespace="ancestorNamespace"
:is-locked-by-admin="isLockedByApplicationSettings"
:is-locked-by-group-ancestor="isLockedByGroupAncestor"
:target-element="targetElement"
/>
</span>
</template>

View File

@ -1,17 +1,17 @@
<script>
/**
* This component is a utility that can be used in a HAML settings pages
* It will get all popover targets and create a popover for each one.
* It will get all tooltip targets and create a tooltip for each one.
* This should not be used in Vue Apps as we we are breaking component isolation.
* Instead, use `lock_popover.vue` and provide a list of vue $refs to loop through.
* Instead, use `lock_tooltip.vue` and provide a list of vue $refs to loop through.
*/
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import LockPopover from './lock_popover.vue';
import LockTooltip from './lock_tooltip.vue';
export default {
name: 'HamlLockPopovers',
name: 'HamlLockTooltips',
components: {
LockPopover,
LockTooltip,
},
data() {
return {
@ -19,14 +19,14 @@ export default {
};
},
mounted() {
this.targets = [...document.querySelectorAll('.js-cascading-settings-lock-popover-target')].map(
this.targets = [...document.querySelectorAll('.js-cascading-settings-lock-tooltip-target')].map(
(el) => {
const {
dataset: { popoverData },
dataset: { tooltipData },
} = el;
const { lockedByAncestor, lockedByApplicationSetting, ancestorNamespace } =
convertObjectPropsToCamelCase(JSON.parse(popoverData || '{}'), { deep: true });
convertObjectPropsToCamelCase(JSON.parse(tooltipData || '{}'), { deep: true });
return {
el,
@ -42,7 +42,7 @@ export default {
<template>
<div>
<lock-popover
<lock-tooltip
v-for="(
{ el, lockedByApplicationSetting, lockedByAncestor, ancestorNamespace }, index
) in targets"

View File

@ -1,10 +1,10 @@
<script>
import { GlPopover, GlSprintf, GlLink } from '@gitlab/ui';
import { GlTooltip, GlSprintf, GlLink } from '@gitlab/ui';
export default {
name: 'LockPopover',
name: 'LockTooltip',
components: {
GlPopover,
GlTooltip,
GlSprintf,
GlLink,
},
@ -24,8 +24,8 @@ export default {
required: true,
},
targetElement: {
required: true,
type: Element,
required: true,
},
},
computed: {
@ -37,9 +37,9 @@ export default {
</script>
<template>
<gl-popover v-if="isLocked" :target="targetElement" placement="top">
<gl-tooltip v-if="isLocked" :target="targetElement" placement="top">
<template #title>{{ s__('CascadingSettings|Setting cannot be changed') }}</template>
<span data-testid="cascading-settings-lock-popover">
<span data-testid="cascading-settings-lock-tooltip">
<template v-if="isLockedByAdmin">{{
s__(
'CascadingSettings|An administrator selected this setting for the instance and you cannot change it.',
@ -61,5 +61,5 @@ export default {
}}
</template>
</span>
</gl-popover>
</gl-tooltip>
</template>

View File

@ -1,15 +1,15 @@
import Vue from 'vue';
import HamlLockPopovers from './components/haml_lock_popovers.vue';
import HamlLockTooltips from './components/haml_lock_tooltips.vue';
export const initCascadingSettingsLockPopovers = () => {
const el = document.querySelector('.js-cascading-settings-lock-popovers');
export const initCascadingSettingsLockTooltips = () => {
const el = document.querySelector('.js-cascading-settings-lock-tooltips');
if (!el) return false;
return new Vue({
el,
render(createElement) {
return createElement(HamlLockPopovers);
return createElement(HamlLockTooltips);
},
});
};

View File

@ -1,5 +1,7 @@
import ProjectsList from '~/projects_list';
import { initYourWorkProjects } from '~/projects/your_work';
import { initProjectsFilteredSearchAndSort } from '~/projects/filtered_search_and_sort';
new ProjectsList(); // eslint-disable-line no-new
initYourWorkProjects();
initProjectsFilteredSearchAndSort();

View File

@ -1,3 +1,6 @@
import { initProjectsExploreFilteredSearchAndSort } from '~/projects/explore';
import { initProjectsFilteredSearchAndSort } from '~/projects/filtered_search_and_sort';
initProjectsExploreFilteredSearchAndSort();
initProjectsFilteredSearchAndSort({
sortEventName: 'use_sort_projects_explore',
filterEventName: 'use_filter_bar_projects_explore',
});

View File

@ -4,7 +4,7 @@ import initFilePickers from '~/file_pickers';
import initTransferGroupForm from '~/groups/init_transfer_group_form';
import { initGroupSelects } from '~/vue_shared/components/entity_select/init_group_selects';
import { initProjectSelects } from '~/vue_shared/components/entity_select/init_project_selects';
import { initCascadingSettingsLockPopovers } from '~/namespaces/cascading_settings';
import { initCascadingSettingsLockTooltips } from '~/namespaces/cascading_settings';
import { initDormantUsersInputSection } from '~/pages/admin/application_settings/account_and_limits';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import initSearchSettings from '~/search_settings';
@ -42,6 +42,6 @@ initGroupSelects();
initProjectSelects();
initSearchSettings();
initCascadingSettingsLockPopovers();
initCascadingSettingsLockTooltips();
initGroupSettingsReadme();

View File

@ -16,17 +16,25 @@ export default {
required: false,
default: null,
},
locked: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<div class="project-feature-row">
<label v-if="label" class="label-bold">
{{ label }}
</label>
<div class="gl-flex">
<label v-if="label" class="label-bold" :class="{ 'gl-text-gray-400': locked }">
{{ label }}
</label>
<slot name="label-icon"></slot>
</div>
<div>
<span v-if="helpText" class="text-muted"> {{ helpText }} </span>
<span v-if="helpText" class="gl-text-gray-400"> {{ helpText }} </span>
<span v-if="helpPath"
><a :href="helpPath" target="_blank">{{ __('Learn more') }}</a
>.</span

View File

@ -114,7 +114,6 @@ export default {
),
},
mixins: [settingsMixin, glFeatureFlagMixin()],
props: {
requestCveAvailable: {
type: Boolean,
@ -1056,6 +1055,7 @@ export default {
:label="$options.i18n.duoLabel"
:help-text="$options.i18n.duoHelpText"
:help-path="$options.duoHelpPath"
:locked="duoFeaturesLocked"
>
<gl-toggle
v-model="duoFeaturesEnabled"

View File

@ -1,7 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import settingsPanel from './components/settings_panel.vue';
@ -20,12 +19,12 @@ export default function initProjectPermissionsSettings() {
const componentProps = JSON.parse(componentPropsEl.innerHTML);
const {
targetFormId,
additionalInformation,
confirmDangerMessage,
confirmButtonText,
showVisibilityConfirmModal,
confirmDangerMessage,
htmlConfirmationMessage,
showVisibilityConfirmModal,
targetFormId,
phrase: confirmationPhrase,
} = mountPoint.dataset;

View File

@ -1,25 +0,0 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import FilteredSearchAndSort from './components/filtered_search_and_sort.vue';
export const initProjectsExploreFilteredSearchAndSort = () => {
const el = document.getElementById('js-projects-explore-filtered-search-and-sort');
if (!el) return false;
const {
dataset: { appData },
} = el;
const { initialSort, programmingLanguages, starredExploreProjectsPath, exploreRootPath } =
convertObjectPropsToCamelCase(JSON.parse(appData));
return new Vue({
el,
name: 'ProjectsExploreFilteredSearchAndSortRoot',
provide: { initialSort, programmingLanguages, starredExploreProjectsPath, exploreRootPath },
render(createElement) {
return createElement(FilteredSearchAndSort);
},
});
};

View File

@ -19,7 +19,7 @@ import {
const trackingMixin = InternalEvents.mixin();
export default {
name: 'ProjectsExploreFilteredSearchAndSort',
name: 'ProjectsFilteredSearchAndSort',
filteredSearch: {
namespace: FILTERED_SEARCH_NAMESPACE,
recentSearchesStorageKey: RECENT_SEARCHES_STORAGE_KEY_PROJECTS,
@ -29,7 +29,13 @@ export default {
FilteredSearchAndSort,
},
mixins: [trackingMixin],
inject: ['initialSort', 'programmingLanguages', 'starredExploreProjectsPath', 'exploreRootPath'],
inject: [
'initialSort',
'programmingLanguages',
'pathsToExcludeSortOn',
'sortEventName',
'filterEventName',
],
computed: {
filteredSearchTokens() {
return [
@ -85,8 +91,7 @@ export default {
return this.queryAsObject?.[FILTERED_SEARCH_TERM_KEY] || '';
},
sortOptions() {
const mostStarredPathnames = [this.starredExploreProjectsPath, this.exploreRootPath];
if (mostStarredPathnames.includes(window.location.pathname)) {
if (this.pathsToExcludeSortOn.includes(window.location.pathname)) {
return [];
}
@ -114,7 +119,7 @@ export default {
isAscending ? SORT_DIRECTION_ASC : SORT_DIRECTION_DESC
}`;
this.trackEvent('use_sort_projects_explore', {
this.trackEvent(this.sortEventName, {
label: sort,
});
@ -126,7 +131,7 @@ export default {
onSortByChange(sortBy) {
const sort = `${sortBy}_${this.isAscending ? SORT_DIRECTION_ASC : SORT_DIRECTION_DESC}`;
this.trackEvent('use_sort_projects_explore', {
this.trackEvent(this.sortEventName, {
label: sort,
});
@ -143,6 +148,10 @@ export default {
queryObject.archived = this.queryAsObject.archived;
}
if (this.queryAsObject.personal) {
queryObject.personal = this.queryAsObject.personal;
}
const trackingProperty = Object.entries(filtersQuery).reduce(
(accumulator, [tokenType, optionValue]) => {
if (tokenType === FILTERED_SEARCH_TERM_KEY) {
@ -160,7 +169,7 @@ export default {
{},
);
this.trackEvent('use_filter_bar_projects_explore', {
this.trackEvent(this.filterEventName, {
label: JSON.stringify(trackingProperty),
});
@ -172,6 +181,7 @@ export default {
<template>
<filtered-search-and-sort
class="gl-w-full"
:filtered-search-namespace="$options.filteredSearch.namespace"
:filtered-search-tokens="filteredSearchTokens"
:filtered-search-term-key="$options.filteredSearch.searchTermKey"

View File

@ -0,0 +1,32 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import FilteredSearchAndSort from './components/filtered_search_and_sort.vue';
export const initProjectsFilteredSearchAndSort = ({ sortEventName, filterEventName } = {}) => {
const el = document.getElementById('js-projects-filtered-search-and-sort');
if (!el) return false;
const {
dataset: { appData },
} = el;
const { initialSort, programmingLanguages, pathsToExcludeSortOn } = convertObjectPropsToCamelCase(
JSON.parse(appData),
);
return new Vue({
el,
name: 'ProjectsFilteredSearchAndSortRoot',
provide: {
initialSort,
programmingLanguages,
pathsToExcludeSortOn,
sortEventName,
filterEventName,
},
render(createElement) {
return createElement(FilteredSearchAndSort);
},
});
};

View File

@ -5,10 +5,10 @@ module NamespacesHelper
params.dig(:project, :namespace_id) || params[:namespace_id]
end
def cascading_namespace_settings_popover_data(attribute, group, settings_path_helper)
def cascading_namespace_settings_tooltip_data(attribute, group, settings_path_helper)
locked_by_ancestor = group.namespace_settings.public_send("#{attribute}_locked_by_ancestor?") # rubocop:disable GitlabSecurity/PublicSend
popover_data = {
tooltip_data = {
locked_by_application_setting: group.namespace_settings.public_send("#{attribute}_locked_by_application_setting?"), # rubocop:disable GitlabSecurity/PublicSend
locked_by_ancestor: locked_by_ancestor
}
@ -16,14 +16,14 @@ module NamespacesHelper
if locked_by_ancestor
ancestor_namespace = group.namespace_settings.public_send("#{attribute}_locked_ancestor").namespace # rubocop:disable GitlabSecurity/PublicSend
popover_data[:ancestor_namespace] = {
tooltip_data[:ancestor_namespace] = {
full_name: ancestor_namespace.full_name,
path: settings_path_helper.call(ancestor_namespace)
}
end
{
popover_data: popover_data.to_json,
tooltip_data: tooltip_data.to_json,
testid: 'cascading-settings-lock-icon'
}
end

View File

@ -308,7 +308,13 @@ module ProjectsHelper
end
def show_projects?(projects, params)
!!(params[:personal] || params[:name] || params[:language] || any_projects?(projects))
!!(
params[:personal] ||
params[:name] ||
params[:language] ||
params[:archived] == 'only' ||
any_projects?(projects)
)
end
def push_to_create_project_command(user = current_user)
@ -640,12 +646,11 @@ module ProjectsHelper
'manual-ordering'
end
def projects_explore_filtered_search_and_sort_app_data
def projects_filtered_search_and_sort_app_data
{
initial_sort: project_list_sort_by,
programming_languages: programming_languages,
starred_explore_projects_path: starred_explore_projects_path,
explore_root_path: explore_root_path
paths_to_exclude_sort_on: [starred_explore_projects_path, explore_root_path]
}.to_json
end

View File

@ -9,12 +9,4 @@
= render Pajamas::ButtonComponent.new(href: new_project_path, variant: :confirm, button_options: { data: { testid: 'new-project-button' } }) do
= _("New project")
.top-area
.scrolling-tabs-container.inner-page-scroll-tabs.gl-grow.gl-basis-0.gl-min-w-0
%button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
= sprite_icon('chevron-lg-left', size: 12)
%button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
= sprite_icon('chevron-lg-right', size: 12)
= render 'dashboard/projects_nav'
.nav-controls
= render 'shared/projects/search_form'
= render 'dashboard/projects_nav'

View File

@ -1,10 +1,16 @@
- is_your_projects_path = current_page?(dashboard_projects_path) || current_page?(root_path)
= gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full nav gl-tabs-nav' }) do
= gl_tab_link_to dashboard_projects_path, { item_active: is_your_projects_path, class: 'shortcuts-activity', data: { placement: 'right' } } do
= s_("ProjectList|Yours")
= gl_tab_counter_badge(limited_counter_with_delimiter(@all_user_projects))
= gl_tab_link_to starred_dashboard_projects_path, { data: { placement: 'right' } } do
= s_("ProjectList|Starred")
= gl_tab_counter_badge(limited_counter_with_delimiter(@all_starred_projects))
= render_if_exists "dashboard/removed_projects_tab"
%div
= gl_tabs_nav({ class: 'gl-flex gl-grow gl-border-none'}) do
= gl_tab_link_to dashboard_projects_path, { item_active: is_your_projects_path && !params['archived'] && !params['personal'], class: 'shortcuts-activity', data: { placement: 'right' } } do
= s_("ProjectList|Yours")
= gl_tab_counter_badge(limited_counter_with_delimiter(@all_user_projects))
= gl_tab_link_to starred_dashboard_projects_path, { data: { placement: 'right' } } do
= s_("ProjectList|Starred")
= gl_tab_counter_badge(limited_counter_with_delimiter(@all_starred_projects))
= gl_tab_link_to _('Personal'), dashboard_projects_path({ personal: true }), { item_active: params['personal'] == 'true' }
= gl_tab_link_to _('Inactive'), dashboard_projects_path({ archived: 'only' }), { item_active: params['archived'] == 'only' }
= render_if_exists "dashboard/removed_projects_tab"
#js-projects-filtered-search-and-sort.gl-py-5.gl-border-t.gl-border-b.gl-w-full{ data: { app_data: projects_filtered_search_and_sort_app_data } }
-# This element takes up space while Vue is rendering to avoid page jump
.gl-h-7

View File

@ -1,4 +0,0 @@
.nav-block
= gl_tabs_nav do
= gl_tab_link_to s_('DashboardProjects|All'), dashboard_projects_path, { item_active: params[:personal].blank? }
= gl_tab_link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true), { item_active: params[:personal].present? }

View File

@ -14,7 +14,6 @@
- else
- if show_projects?(@projects, params)
= render 'dashboard/projects_head'
= render 'nav'
= render 'projects'
- else
= render "zero_authorized_projects"

View File

@ -6,6 +6,6 @@
= gl_tab_link_to _('Inactive'), explore_projects_path({ archived: 'only' }), { item_active: current_page?(explore_projects_path) && params['archived'] == 'only', data: { event_tracking: 'click_tab_projects_explore', event_label: 'Inactive' } }
= gl_tab_link_to _('All'), explore_projects_path({ archived: true }), { item_active: current_page?(explore_projects_path) && params['archived'] == 'true', data: { event_tracking: 'click_tab_projects_explore', event_label: 'All' } }
#js-projects-explore-filtered-search-and-sort.gl-py-5.gl-border-t.gl-border-b{ data: { app_data: projects_explore_filtered_search_and_sort_app_data } }
#js-projects-filtered-search-and-sort.gl-py-5.gl-border-t.gl-border-b{ data: { app_data: projects_filtered_search_and_sort_app_data } }
-# This element takes up space while Vue is rendering to avoid page jump
.gl-h-7

View File

@ -3,7 +3,7 @@
- expanded = expanded_by_default?
- @force_desktop_expanded_sidebar = true
= render 'shared/namespaces/cascading_settings/lock_popovers'
= render 'shared/namespaces/cascading_settings/lock_tooltips'
%h1.gl-sr-only= @breadcrumb_title

View File

@ -1,3 +1,3 @@
- class_list = local_assigns.fetch(:class_list, '')
= render Pajamas::ButtonComponent.new(category: 'tertiary', icon: 'lock', button_options: { class: "gl-absolute gl-top-3 gl-right-0 -gl-translate-y-1/2 gl-p-1! gl-bg-transparent! gl-cursor-default! js-cascading-settings-lock-popover-target #{class_list}", data: cascading_namespace_settings_popover_data(attribute, group, settings_path_helper) })
= render Pajamas::ButtonComponent.new(category: 'tertiary', icon: 'lock', button_options: { class: "gl-absolute gl-top-3 gl-right-0 -gl-translate-y-1/2 !gl-p-1 !gl-bg-transparent !gl-cursor-default js-cascading-settings-lock-tooltip-target #{class_list}", data: cascading_namespace_settings_tooltip_data(attribute, group, settings_path_helper) })

View File

@ -1 +0,0 @@
.js-cascading-settings-lock-popovers

View File

@ -0,0 +1 @@
.js-cascading-settings-lock-tooltips

View File

@ -0,0 +1,8 @@
---
name: request_authenticator_exclude_job_token_basic_auth
introduced_by_url: https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4322
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/474769
milestone: "17.3"
type: development
group: group::pipeline security
default_enabled: false

View File

@ -11,3 +11,5 @@ analytics_instrumentation.check_usage_data_insertions!
analytics_instrumentation.check_deprecated_data_sources!
analytics_instrumentation.check_removed_metric_fields!
analytics_instrumentation.warn_about_migrated_redis_keys_specs!

View File

@ -895,10 +895,10 @@ fetch responses. This can reduce server load when your server receives
lots of CI fetch traffic.
The pack-objects cache wraps `git pack-objects`, an internal part of
Git that gets invoked indirectly via the PostUploadPack and
Git that gets invoked indirectly by using the PostUploadPack and
SSHUploadPack Gitaly RPCs. Gitaly runs PostUploadPack when a
user does a Git fetch via HTTP, or SSHUploadPack when a
user does a Git fetch via SSH.
user does a Git fetch by using HTTP, or SSHUploadPack when a
user does a Git fetch by using SSH.
When the cache is enabled, anything that uses PostUploadPack or SSHUploadPack can
benefit from it. It is orthogonal to:

View File

@ -10,7 +10,7 @@ DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
Discussions are a set of related notes on:
Discussions are attached to:
- Snippets
- Issues
@ -18,11 +18,24 @@ Discussions are a set of related notes on:
- Merge requests
- Commits
This includes [comments and threads](../user/discussions/index.md) and system notes.
This includes [comments, threads](../user/discussions/index.md), and system notes.
System notes are notes about changes to the object (for example, when a milestone changes).
Label notes are not part of this API, but recorded as separate events in
[resource label events](resource_label_events.md).
## Understand note types in the API
Not all discussion types are equally available in the API:
- **Note**: A comment left on the _root_ of an issue, merge request, commit,
or snippet.
- **Discussion**: A collection, often called a _thread_, of `DiscussionNotes` in
an issue, merge request, commit, or snippet.
- **DiscussionNote**: An individual item in a discussion on an issue, merge request,
commit, or snippet. These are not returned as part of the Note API.
Not available in the [Events API](events.md).
## Discussions pagination
By default, `GET` requests return 20 results at a time because the API results are paginated.

View File

@ -24,13 +24,15 @@ Available target types for the `target_type` parameter are:
- `issue`
- `milestone`
- `merge_request`
- `note`
- `note` - Some notes on merge requests may be of the type `DiscussionNote`, instead of `Note`.
`DiscussionNote` items are [not available using the API](discussions.md#understand-note-types-in-the-api).
- `project`
- `snippet`
- `user`
These options are in lowercase.
Events associated with epics are not available using the API.
Some discussions on merge requests may be of type `DiscussionNote`. These are not available using the API.
### Date formatting
@ -55,14 +57,14 @@ GET /events
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `action` | string | no | Include only events of a particular [action type](#actions) |
| `target_type` | string | no | Include only events of a particular [target type](#target-types) |
| `before` | date | no | Include only events created before a particular date. [View how to format dates](#date-formatting). |
| `after` | date | no | Include only events created after a particular date. [View how to format dates](#date-formatting). |
| `scope` | string | no | Include all events across a user's projects. |
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc`. |
| Attribute | Type | Required | Description |
|---------------|--------|----------|-----------------------------------------------------------------------------------------------------|
| `action` | string | no | Include only events of a particular [action type](#actions) |
| `target_type` | string | no | Include only events of a particular [target type](#target-types) |
| `before` | date | no | Include only events created before a particular date. [View how to format dates](#date-formatting). |
| `after` | date | no | Include only events created after a particular date. [View how to format dates](#date-formatting). |
| `scope` | string | no | Include all events across a user's projects. |
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc`. |
Example request:
@ -134,16 +136,16 @@ GET /users/:id/events
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID or Username of the user |
| `action` | string | no | Include only events of a particular [action type](#actions) |
| `target_type` | string | no | Include only events of a particular [target type](#target-types) |
| `before` | date | no | Include only events created before a particular date. [View how to format dates](#date-formatting). |
| `after` | date | no | Include only events created after a particular date. [View how to format dates](#date-formatting). |
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc`. |
| `page` | integer | no | The page of results to return. Defaults to 1. |
| `per_page` | integer | no | The number of results per page. Defaults to 20. |
| Attribute | Type | Required | Description |
|---------------|---------|----------|-----------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID or Username of the user |
| `action` | string | no | Include only events of a particular [action type](#actions) |
| `target_type` | string | no | Include only events of a particular [target type](#target-types) |
| `before` | date | no | Include only events created before a particular date. [View how to format dates](#date-formatting). |
| `after` | date | no | Include only events created after a particular date. [View how to format dates](#date-formatting). |
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc`. |
| `page` | integer | no | The page of results to return. Defaults to 1. |
| `per_page` | integer | no | The number of results per page. Defaults to 20. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/users/:id/events"
@ -284,14 +286,14 @@ GET /projects/:project_id/events
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `project_id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `action` | string | no | Include only events of a particular [action type](#actions) |
| `target_type` | string | no | Include only events of a particular [target type](#target-types) |
| `before` | date | no | Include only events created before a particular date. [View how to format dates](#date-formatting). |
| `after` | date | no | Include only events created after a particular date. [View how to format dates](#date-formatting). |
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc`. |
| Attribute | Type | Required | Description |
|---------------|----------------|----------|-----------------------------------------------------------------------------------------------------|
| `project_id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `action` | string | no | Include only events of a particular [action type](#actions) |
| `target_type` | string | no | Include only events of a particular [target type](#target-types) |
| `before` | date | no | Include only events created before a particular date. [View how to format dates](#date-formatting). |
| `after` | date | no | Include only events created after a particular date. [View how to format dates](#date-formatting). |
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc`. |
Example request:

View File

@ -29,7 +29,7 @@ See also:
These styles are not backed by a RuboCop rule.
For every style added to this section, link the discussion from the section's [history note](../documentation/versions.md#add-a-history-item) to provide context and serve as a reference.
For every style added to this section, link the discussion from the section's [history note](../documentation/styleguide/availability_details.md#history) to provide context and serve as a reference.
### Instance variable access using `attr_reader`

View File

@ -159,15 +159,15 @@ Renders the label for a `fieldset` setting.
| `settings_path_helper` | Lambda function that generates a path to the ancestor setting. For example, `-> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') }` | `Lambda` | `true` |
| `help_text` | Text shown below the checkbox. | `String` | `false` (`nil`) |
[`_lock_popovers.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/views/shared/namespaces/cascading_settings/_lock_popovers.html.haml)
[`_lock_tooltips.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/views/shared/namespaces/cascading_settings/_lock_tooltips.html.haml)
Renders the mount element needed to initialize the JavaScript used to display the popover when hovering over the lock icon. This partial is only needed once per page.
Renders the mount element needed to initialize the JavaScript used to display the tooltip when hovering over the lock icon. This partial is only needed once per page.
### JavaScript
[`initCascadingSettingsLockPopovers`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/assets/javascripts/namespaces/cascading_settings/index.js#L4)
[`initCascadingSettingsLockTooltips`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/assets/javascripts/namespaces/cascading_settings/index.js#L4)
Initializes the JavaScript needed to display the popover when hovering over the lock icon (**{lock}**).
Initializes the JavaScript needed to display the tooltip when hovering over the lock icon (**{lock}**).
This function should be imported and called in the [page-specific JavaScript](fe_guide/performance.md#page-specific-javascript).
### Put it all together
@ -175,7 +175,7 @@ This function should be imported and called in the [page-specific JavaScript](fe
```haml
-# app/views/groups/edit.html.haml
= render 'shared/namespaces/cascading_settings/lock_popovers'
= render 'shared/namespaces/cascading_settings/lock_tooltips'
- delayed_project_removal_locked = cascading_namespace_setting_locked?(:delayed_project_removal, @group)
- merge_method_locked = cascading_namespace_setting_locked?(:merge_method, @group)
@ -222,7 +222,7 @@ This function should be imported and called in the [page-specific JavaScript](fe
```javascript
// app/assets/javascripts/pages/groups/edit/index.js
import { initCascadingSettingsLockPopovers } from '~/namespaces/cascading_settings';
import { initCascadingSettingsLockTooltips } from '~/namespaces/cascading_settings';
initCascadingSettingsLockPopovers();
initCascadingSettingsLockTooltips();
```

View File

@ -102,4 +102,4 @@ Related Handbook pages:
## Update the related documentation
When features are deprecated and removed, [update the related documentation](../documentation/versions.md#deprecations-and-removals).
When features are deprecated and removed, [update the related documentation](../documentation/styleguide/deprecations_and_removals.md).

View File

@ -141,3 +141,16 @@ in the `gitlab-docs-archives` repository.
1. After the pipeline finishes, go to `https://archives.docs.gitlab.com` and verify
that the changes are available for the correct version.
## View older documentation versions
Previous versions of the documentation are available on `docs.gitlab.com`.
To view a previous version, in the upper-right corner, select the version
number from the dropdown list.
To view versions that are not available on `docs.gitlab.com`:
- View the [documentation archives](https://docs.gitlab.com/archives/).
- Go to the GitLab repository and select the version-specific branch. For example,
the [13.2 branch](https://gitlab.com/gitlab-org/gitlab/-/tree/13-2-stable-ee/doc) has the
documentation for GitLab 13.2.

View File

@ -42,7 +42,9 @@ the list of users testing this feature, do this thing. If you find a bug,
[open an issue](https://link).
```
When the feature is ready for production, remove:
## When features become generally available
When the feature changes from experiment or beta to generally available, remove:
- The **Status** from the availability details.
- Any language about the feature not being ready for production in the body
@ -50,3 +52,14 @@ When the feature is ready for production, remove:
- The feature flag information if available.
Ensure the history is up-to-date by adding a note about the production release.
### GitLab Duo features
When a GitLab Duo feature becomes generally available:
- In the [top-level GitLab Duo page](../../user/gitlab_duo/index.md), move the topic from the
`Beta features` or `Experimental features` section to the `Generally available features` section.
- If the feature is documented in [GitLab Duo experiments](../../user/gitlab_duo/experiments.md),
move the content somewhere more appropriate (to other related features).
- Make sure you update the history and status values, including any
[add-on information](styleguide/availability_details.md#gitlab-duo-pro-or-enterprise-add-on).

View File

@ -44,7 +44,7 @@ To document feature flags:
## Add history text
When the state of a flag changes (for example, from disabled by default to enabled by default), add the change to the
[history](versions.md#add-a-history-item).
[history](../documentation/styleguide/availability_details.md#history).
Possible history entries are:

View File

@ -97,7 +97,7 @@ Example response:
## History
Add [history](versions.md#documenting-version-specific-features)
Add [history](../documentation/styleguide/availability_details.md#history)
to describe new or updated API calls.
To add history for an individual attribute, include it in the history
@ -115,7 +115,7 @@ If the API or attribute is deployed behind a feature flag,
## Deprecations
To document the deprecation of an API endpoint, follow the steps to
[deprecate a page or topic](versions.md#deprecate-a-page-or-topic).
[deprecate a page or topic](../documentation/styleguide/deprecations_and_removals.md).
To deprecate an attribute:

View File

@ -27,7 +27,7 @@ DETAILS:
## Available options
Use the following text for the tier, offering, and status.
Use the following text for the tier, offering, status, and version history.
### Offering
@ -52,18 +52,9 @@ For tier, choose one:
- `Premium, Ultimate`
- `Ultimate`
### Status
#### GitLab Duo Pro or Enterprise add-on
For status, choose one:
- `Beta`
- `Experiment`
Generally available features should not have a status.
### GitLab Duo Pro or Enterprise add-on
The add-ons belong with other subscription tiers. Document them by using the phrase `with` and the add-on.
Document add-ons by using the phrase `with` and the add-on.
For example, `with GitLab Duo Pro`.
The possibilities are:
@ -87,6 +78,116 @@ You might have to differentiate which add-on applies for each offering (GitLab.c
NOTE:
GitLab Dedicated always includes an Ultimate subscription.
### Status
For status, choose one:
- `Beta`
- `Experiment`
Generally available features should not have a status.
### History
For version history, include these words in this order. Capitalization doesn't matter (with the exception of `GitLab`).
- `introduced`, `added`, `enabled`, `deprecated`, `changed`, `moved`, `recommended`, `removed`, or `renamed`
- `in` or `to`
- `GitLab` (or, for external projects, the name of the project)
The docs site uses [Ruby code](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/lib/filters/introduced_in.rb) to render the version history based on these words.
In addition:
- Ensure that the output generates properly.
- Ensure the version history begins with `> -`.
- If possible, include a link to the related issue, merge request, or epic.
- Do not link to the pricing page. Do not include the subscription tier.
#### Updated features
For features that have changed or been updated, add a new list item.
Start the sentence with the feature name or a gerund.
For example:
```markdown
> - [Introduced](https://issue-link) in GitLab 13.1.
> - Creating an issue from an issue board [introduced](https://issue-link) in GitLab 14.1.
```
Or:
```markdown
> - [Introduced](https://issue-link) in GitLab 13.1.
> - Notifications for expiring tokens [introduced](https://issue-link) in GitLab 14.3.
```
#### Moved subscription tiers
For features that move to another subscription tier, use `moved`:
```markdown
> - [Moved](https://issue-link) from GitLab Ultimate to GitLab Premium in 11.8.
> - [Moved](https://issue-link) from GitLab Premium to GitLab Free in 12.0.
```
#### Changed feature status
For a feature status change from experiment to beta, use `changed`:
```markdown
> - [Introduced](https://issue-link) as an [experiment](../../policy/experiment-beta-support.md) in GitLab 15.7.
> - [Changed](https://issue-link) to beta in GitLab 16.0.
```
For a change to generally available, use:
```markdown
> - [Generally available](https://issue-link) in GitLab 16.10.
```
#### Features made available as part of a program
For features made available to users as part of a program, add a new list item and link to the program.
```markdown
> - [Introduced](https://issue-link) in GitLab 15.1.
> - Merged results pipelines [added](https://issue-link) to the [Registration Features Program](https://page-link) in GitLab 16.7.
```
#### Features behind feature flags
For features introduced behind feature flags, add details about the feature flag. For more information, see [Document features deployed behind feature flags](../feature_flags.md).
#### Removing versions
Remove history items and inline text that refer to unsupported versions.
GitLab supports the current major version and two previous major versions.
For example, if 16.0 is the current major version, all major and minor releases of
GitLab 16.0, 15.0, and 14.0 are supported.
For the list of current supported versions, see [Version support](https://about.gitlab.com/support/statement-of-support/#version-support).
Remove information about [features behind feature flags](../feature_flags.md)
only if all events related to the feature flag happened in unsupported versions.
If the flag hasn't been removed, readers should know when it was introduced.
#### Timing version removals
When a new major version is about to be released, create merge
requests to remove mentions of the last unsupported version. Only merge
them during the milestone of the new major release.
For example, if GitLab 17.0 is the next major upcoming release:
- The supported versions are 16, 15, and 14.
- When GitLab 17.0 is released, GitLab 14 is no longer supported.
Create merge requests to remove mentions of GitLab 14, but only
merge them during the 17.0 milestone, after 16.11 is released.
## When to add availability details
Assign availability details under:
@ -163,6 +264,17 @@ IDs of the users to assign the issue to. Ultimate only.
For more examples, see the [REST API style guide](../restful_api_styleguide.md).
## Inline history text
If you're adding content to an existing topic, add historical information
inline with the existing text. If possible, include a link to the related issue,
merge request, or epic. For example:
```markdown
The voting strategy [in GitLab 13.4 and later](https://issue-link) requires the primary and secondary
voters to agree.
```
## Administrator documentation for availability details
Topics that are only for instance administrators should have the `Self-managed` tier.

View File

@ -0,0 +1,160 @@
---
info: For assistance with this Style Guide page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
stage: none
group: unassigned
description: 'Guidelines for deprecations and page removals'
---
## Deprecations and removals
When GitLab deprecates or removes a feature, use the following process to update the documentation.
This process requires temporarily changing content to be "deprecated" or "removed" before it's deleted.
If a feature is not generally available, you can delete the content outright instead of following these instructions.
NOTE:
A separate process exists for [GraphQL docs](../../api_graphql_styleguide.md#deprecating-schema-items)
and [REST API docs](../restful_api_styleguide.md#deprecations).
### Deprecate a page or topic
To deprecate a page or topic:
1. Add `(deprecated)` after the title. Use a warning to explain when it was deprecated,
when it will be removed, and the replacement feature.
```markdown
## Title (deprecated)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
WARNING:
This feature was [deprecated](https://issue-link) in GitLab 14.8
and is planned for removal in 15.4. Use [feature X](link-to-docs.md) instead.
```
If you're not sure when the feature will be removed or no
replacement feature exists, you don't need to add this information.
1. If the deprecation is a [breaking change](../../../update/terminology.md#breaking-change), add this text:
```markdown
This change is a breaking change.
```
You can add any additional context-specific details that might help users.
1. Add the following HTML comments above and below the content. For `remove_date`,
set a date three months after the [release where it will be removed](https://about.gitlab.com/releases/).
```markdown
<!--- start_remove The following content will be removed on remove_date: 'YYYY-MM-DD' -->
## Title (deprecated)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
WARNING:
This feature was [deprecated](https://issue-link) in GitLab 14.8
and is planned for removal in 15.4. Use [feature X](link-to-docs.md) instead.
<!--- end_remove -->
```
1. Open a merge request to add the word `(deprecated)` to the left nav, after the page title.
### Remove a page
Mark content as removed during the release the feature was removed.
The title and a removed indicator remains until three months after the removal.
To remove a page:
1. Leave the page title. Remove all other content, including the history items and the word `WARNING:`.
1. After the title, change `(deprecated)` to `(removed)`.
1. Update the YAML metadata:
- For `remove_date`, set the value to a date three months after
the release when the feature was removed.
- For the `redirect_to`, set a path to a file that makes sense. If no obvious
page exists, use the docs home page.
```markdown
---
stage: Data Stores
group: Global Search
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
remove_date: '2022-08-02'
redirect_to: '../newpath/to/file/index.md'
---
# Title (removed)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
This feature was [deprecated](https://issue-link) in GitLab X.Y
and [removed](https://issue-link) in X.Y.
Use [feature X](link-to-docs.md) instead.
```
1. Remove the page's entry from the global navigation by editing [`navigation.yaml`](https://gitlab.com/gitlab-org/gitlab-docs/blob/main/content/_data/navigation.yaml) in `gitlab-docs`.
This content is removed from the documentation as part of the Technical Writing team's
[regularly scheduled tasks](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#regularly-scheduled-tasks).
### Remove a topic
To remove a topic:
1. Leave the title and the details of the deprecation and removal. Remove all other content,
including the history items and the word `WARNING:`.
1. Add `(removed)` after the title.
1. Add the following HTML comments above and below the topic.
For `remove_date`, set a date three months after the release where it was removed.
```markdown
<!--- start_remove The following content will be removed on remove_date: 'YYYY-MM-DD' -->
## Title (removed)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
This feature was [deprecated](https://issue-link) in GitLab X.Y
and [removed](https://issue-link) in X.Y.
Use [feature X](link-to-docs.md) instead.
<!--- end_remove -->
```
This content is removed from the documentation as part of the Technical Writing team's
[regularly scheduled tasks](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#regularly-scheduled-tasks).
### Removing version-specific upgrade pages
Version-specific upgrade pages are in the `doc/update/versions/` directory.
We don't remove version-specific upgrade pages immediately for a major milestone. This gives
users time to upgrade from older versions.
For example, `doc/update/versions/14_changes.md` should
be removed during the `.3` milestone. Therefore `14_changes.md` are
removed in GitLab 17.3.
Instead of removing the unsupported page:
- [Add a note](#remove-a-topic) with a date three months
in the future to ensure the page is removed during the
[monthly maintenance task](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#regularly-scheduled-tasks).
- Do not add `Removed` to the title.
If the `X_changes.md` page contains relative links to other sections
that are removed as part of the versions cleanup, the `docs-lint links`
job might fail. You can replace those relative links with an [archived version](https://archives.docs.gitlab.com).
Choose the latest minor version of the unsupported version to be removed.

View File

@ -1626,8 +1626,31 @@ This is something to be warned about.
### Disclaimer
Use to describe future functionality only.
For more information, see [Legal disclaimer for future features](../versions.md#legal-disclaimer-for-future-features).
If you **must** write about features we have not yet delivered, put this exact disclaimer about forward-looking statements near the content it applies to.
```markdown
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.
It is important to note that the information presented is for informational purposes only.
Please do not rely on this information for purchasing or planning purposes.
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the
sole discretion of GitLab Inc.
```
It renders on the GitLab documentation site as:
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.
It is important to note that the information presented is for informational purposes only.
Please do not rely on this information for purchasing or planning purposes.
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the
sole discretion of GitLab Inc.
If all of the content on the page is not available, use the disclaimer about forward-looking statements once at the top of the page.
If the content in a topic is not ready, use the disclaimer in the topic.
For more information, see [Promising features in future versions](#promising-features-in-future-versions).
### Details
@ -1721,6 +1744,22 @@ Do not copy and paste content from other sources unless it is a limited
quotation with the source cited. Typically it is better to rephrase
relevant information in your own words or link out to the other source.
## Promising features in future versions
Do not promise to deliver features in a future release. For example, avoid phrases like,
"Support for this feature is planned."
We cannot guarantee future feature work, and promises
like these can raise legal issues. Instead, say that an issue exists.
For example:
- Support for improvements is proposed in `[issue <issue_number>](https://link-to-issue)`.
- You cannot do this thing, but `[issue 12345](https://link-to-issue)` proposes to change this behavior.
You can say that we plan to remove a feature.
If you must document a future feature, use the [disclaimer](#disclaimer).
## Products and features
Refer to the information in this section when describing products and features

View File

@ -2286,7 +2286,7 @@ Sometimes you might need to use **yet** when writing a task. If you use
**yet**, ensure the surrounding phrases are written
in present tense, active voice.
[View guidance about how to write about future features](../versions.md#promising-features-in-future-versions).
[View guidance about how to write about future features](index.md#promising-features-in-future-versions).
## you, your, yours

View File

@ -1,351 +1,11 @@
---
info: For assistance with this Style Guide page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
stage: none
group: unassigned
description: 'Writing styles, markup, formatting, and other standards for GitLab Documentation.'
redirect_to: '../documentation/styleguide/availability_details.md'
remove_date: '2024-11-02'
---
# Documenting product versions
This document was moved to [another location](../documentation/styleguide/availability_details.md).
The GitLab product documentation includes version-specific information,
including when features were introduced and when they were updated or removed.
## View older documentation versions
Previous versions of the documentation are available on `docs.gitlab.com`.
To view a previous version, in the upper-right corner, select **Versions**.
To view versions that are not available on `docs.gitlab.com`:
- View the [documentation archives](https://docs.gitlab.com/archives/).
- Go to the GitLab repository and select the version-specific branch. For example,
the [13.2 branch](https://gitlab.com/gitlab-org/gitlab/-/tree/13-2-stable-ee/doc) has the
documentation for GitLab 13.2.
## Documenting version-specific features
When a feature is added or updated, update the documentation with
a **History** list item or as an inline text reference.
You do not need to add historical information on the pages in the `/development` directory.
### Add a **History** item
If all content in a topic is related, add a history item after the topic title.
For example:
```markdown
## Feature name
> - [Introduced](https://issue-link) in GitLab 11.3.
This feature does something.
```
The item text must include these words in order. Capitalization doesn't matter.
- `introduced`, `added`, `enabled`, `deprecated`, `changed`, `moved`, `recommended`, `removed`, or `renamed`
- `in` or `to`
- `GitLab` (or, for external projects, the name of the project)
The docs site uses [Ruby code](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/lib/filters/introduced_in.rb)
to render the notes based on these words.
In addition:
- Try to be consistent with other notes on the page, or other notes on the docs site.
- Ensure that the output generates properly.
- If possible, include a link to the related issue, merge request, or epic.
- Do not link to the pricing page. Do not include the subscription tier.
- Even if you have only one item, ensure it begins with `> -`.
#### Documenting updates to a feature
When a feature is changed or updated, add a new list item.
Start the sentence with the feature name or a gerund.
For example, on the issue boards page:
```markdown
> - [Introduced](https://issue-link) in GitLab 13.1.
> - Creating an issue from an issue board [introduced](https://issue-link) in GitLab 14.1.
```
Or on email notifications page:
```markdown
> - [Introduced](https://issue-link) in GitLab 13.1.
> - Notifications for expiring tokens [introduced](https://issue-link) in GitLab 14.3.
```
#### Making features available as part of a program
When a feature is made available to users as a part of a program, add a new list item.
```markdown
> - [Introduced](https://issue-link) in GitLab 15.1.
> - Merged results pipelines [added](https://issue-link) to the [Registration Features Program](https://page-link) in GitLab 16.7.
```
#### Moving subscription tiers
If a feature is moved to another subscription tier, use `moved`:
```markdown
> - [Moved](https://issue-link) from GitLab Ultimate to GitLab Premium in 11.8.
> - [Moved](https://issue-link) from GitLab Premium to GitLab Free in 12.0.
```
#### Changing the feature status
If the feature status changes to experiment or beta, use `changed`:
```markdown
> - [Introduced](https://issue-link) as an [experiment](../../policy/experiment-beta-support.md) in GitLab 15.7.
> - [Changed](https://issue-link) to beta in GitLab 16.0.
```
For a change to generally available, use:
```markdown
> - [Generally available](https://issue-link) in GitLab 16.10.
```
#### Features introduced behind feature flags
When features are introduced behind feature flags, you must add details about the feature flag to the documentation.
For more information, see [Document features deployed behind feature flags](feature_flags.md).
### Inline history text
If you're adding content to an existing topic, you can add historical information
inline with the existing text. If possible, include a link to the related issue,
merge request, or epic. For example:
```markdown
The voting strategy [in GitLab 13.4 and later](https://issue-link) requires the primary and secondary
voters to agree.
```
## Deprecations and removals
When GitLab deprecates or removes a feature, use the following process to update the documentation.
This process requires temporarily changing content to be "deprecated" or "removed" before it's deleted.
If a feature is not generally available, you can delete the content outright instead of following these instructions.
NOTE:
A separate process exists for [GraphQL docs](../api_graphql_styleguide.md#deprecating-schema-items)
and [REST API docs](restful_api_styleguide.md#deprecations).
### Deprecate a page or topic
To deprecate a page or topic:
1. Add `(deprecated)` after the title. Use a warning to explain when it was deprecated,
when it will be removed, and the replacement feature.
```markdown
## Title (deprecated)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
WARNING:
This feature was [deprecated](https://issue-link) in GitLab 14.8
and is planned for removal in 15.4. Use [feature X](link-to-docs.md) instead.
```
If you're not sure when the feature will be removed or no
replacement feature exists, you don't need to add this information.
1. If the deprecation is a [breaking change](../../update/terminology.md#breaking-change), add this text:
```markdown
This change is a breaking change.
```
You can add any additional context-specific details that might help users.
1. Add the following HTML comments above and below the content. For `remove_date`,
set a date three months after the [release where it will be removed](https://about.gitlab.com/releases/).
```markdown
<!--- start_remove The following content will be removed on remove_date: 'YYYY-MM-DD' -->
## Title (deprecated)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
WARNING:
This feature was [deprecated](https://issue-link) in GitLab 14.8
and is planned for removal in 15.4. Use [feature X](link-to-docs.md) instead.
<!--- end_remove -->
```
1. Open a merge request to add the word `(deprecated)` to the left nav, after the page title.
### Remove a page
Mark content as removed during the release the feature was removed.
The title and a removed indicator remains until three months after the removal.
To remove a page:
1. Leave the page title. Remove all other content, including the history items and the word `WARNING:`.
1. After the title, change `(deprecated)` to `(removed)`.
1. Update the YAML metadata:
- For `remove_date`, set the value to a date three months after
the release when the feature was removed.
- For the `redirect_to`, set a path to a file that makes sense. If no obvious
page exists, use the docs home page.
```markdown
---
stage: Data Stores
group: Global Search
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
remove_date: '2022-08-02'
redirect_to: '../newpath/to/file/index.md'
---
# Title (removed)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
This feature was [deprecated](https://issue-link) in GitLab X.Y
and [removed](https://issue-link) in X.Y.
Use [feature X](link-to-docs.md) instead.
```
1. Remove the page's entry from the global navigation by editing [`navigation.yaml`](https://gitlab.com/gitlab-org/gitlab-docs/blob/main/content/_data/navigation.yaml) in `gitlab-docs`.
This content is removed from the documentation as part of the Technical Writing team's
[regularly scheduled tasks](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#regularly-scheduled-tasks).
### Remove a topic
To remove a topic:
1. Leave the title and the details of the deprecation and removal. Remove all other content,
including the history items and the word `WARNING:`.
1. Add `(removed)` after the title.
1. Add the following HTML comments above and below the topic.
For `remove_date`, set a date three months after the release where it was removed.
```markdown
<!--- start_remove The following content will be removed on remove_date: 'YYYY-MM-DD' -->
## Title (removed)
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
This feature was [deprecated](https://issue-link) in GitLab X.Y
and [removed](https://issue-link) in X.Y.
Use [feature X](link-to-docs.md) instead.
<!--- end_remove -->
```
This content is removed from the documentation as part of the Technical Writing team's
[regularly scheduled tasks](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#regularly-scheduled-tasks).
## Which versions are removed
GitLab supports the current major version and two previous major versions.
For example, if 16.0 is the current major version, all major and minor releases of
GitLab 16.0, 15.0, and 14.0 are supported.
[View the list of supported versions](https://about.gitlab.com/support/statement-of-support/#version-support).
If you see history items or inline text that refers to unsupported versions, you can remove it.
In the history, remove information about [features behind feature flags](feature_flags.md)
only if all events related to the feature flag happened in unsupported versions.
If the flag hasn't been removed, readers should know when it was introduced.
Historical feature information is available in [release posts](https://about.gitlab.com/releases/)
or by searching for the issue or merge request where the work was done.
### Timing of removals
When a new major version is about to be released, you can start creating merge
requests to remove any mentions of the last unsupported version, but only merge
them during the milestone of the new major release.
For example, if GitLab 17.0 is the new major upcoming release:
- The supported versions are 16, 15, and 14.
- When GitLab 17.0 is released, GitLab 14 is no longer supported.
You can then create merge requests to remove any mentions to GitLab 14, but only
merge them during the 17.0 milestone, which is after 16.11 is released.
### Exception for upgrade pages
The [version-specific pages](../../update/index.md#version-specific-upgrading-instructions) are the only exception to the previous guideline.
For example, `doc/update/versions/14_changes.md` should
be removed during the `.3` milestone. In this example, the changes would be removed in 17.3.
We don't remove those pages immediately so that users have time to upgrade
from older versions.
Instead of removing the unsupported page,
[add a note](#remove-a-topic) with a date three months in the future.
This note ensures the page is cleaned up as part of the
[monthly maintenance tasks](https://handbook.gitlab.com/handbook/product/ux/technical-writing/#regularly-scheduled-tasks).
Also, if the `X_changes.md` page contains relative links to other sections
that are removed as part of the versions cleanup, the `docs-lint links`
job will likely fail. You can replace those relative links with an archived
version. Be sure to pick the latest minor version of the
unsupported version to be removed as shown in
<https://archives.docs.gitlab.com/>.
## Promising features in future versions
Do not promise to deliver features in a future release. For example, avoid phrases like,
"Support for this feature is planned."
We cannot guarantee future feature work, and promises
like these can raise legal issues. Instead, say that an issue exists.
For example:
- Support for improvements is proposed in `[issue <issue_number>](https://link-to-issue)`.
- You cannot do this thing, but `[issue 12345](https://link-to-issue)` proposes to change this behavior.
You can say that we plan to remove a feature.
### Legal disclaimer for future features
If you **must** write about features we have not yet delivered, put this exact disclaimer about forward-looking statements near the content it applies to.
```markdown
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.
It is important to note that the information presented is for informational purposes only.
Please do not rely on this information for purchasing or planning purposes.
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the
sole discretion of GitLab Inc.
```
It renders on the GitLab documentation site as:
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.
It is important to note that the information presented is for informational purposes only.
Please do not rely on this information for purchasing or planning purposes.
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the
sole discretion of GitLab Inc.
If all of the content on the page is not available, use the disclaimer about forward-looking statements once at the top of the page.
If the content in a topic is not ready, use the disclaimer in the topic.
<!-- This redirect file can be deleted after <2024-11-02>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -430,7 +430,7 @@ You must announce any deprecation [no later than the third milestone preceding i
To deprecate an integration:
- [Add a deprecation entry](../../development/deprecation_guidelines/index.md#update-the-deprecations-and-removals-documentation).
- [Mark the integration documentation as deprecated](../../development/documentation/versions.md#deprecate-a-page-or-topic).
- [Mark the integration documentation as deprecated](../../development/documentation/styleguide/deprecations_and_removals.md).
- Optional. To prevent any new project-level records from
being created, add the integration to `Project#disabled_integrations` (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114835)).
@ -443,7 +443,7 @@ In the major milestone of intended removal (M.0), disable the integration and de
- Remove the integration from `Integration::INTEGRATION_NAMES`.
- Delete the integration model's `#execute` and `#test` methods (if defined), but keep the model.
- Add a post-migration to delete the integration records from PostgreSQL (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114721)).
- [Mark the integration documentation as removed](../../development/documentation/versions.md#remove-a-page).
- [Mark the integration documentation as removed](../../development/documentation/styleguide/deprecations_and_removals.md#remove-a-page).
- [Update the integration API documentation](../../api/integrations.md).
In the next minor release (M.1):

View File

@ -169,9 +169,12 @@ after more changes are added to the merge request:
clear the **Remove all approvals** checkbox.
1. Select **Save changes**.
Approvals aren't removed when a merge request is
[rebased from the UI](../methods/index.md#rebasing-in-semi-linear-merge-methods)
However, approvals are reset if the target branch changes.
GitLab uses [`git patch-id`](https://git-scm.com/docs/git-patch-id) to identify diffs
in merge requests. This value is a reasonably stable and unique identifier, and it enables
smarter decisions about resetting approvals inside a merge request. When you push new changes
to a merge request, the `patch-id` is evaluated against the previous `patch-id` to determine
if the approvals should be reset. This enables GitLab to make better reset decisions when
you perform commands like `git rebase` or `git merge <target>` on a feature branch.
## Remove approvals by Code Owners if their files changed

View File

@ -38,7 +38,7 @@ module Gitlab
find_user_from_web_access_token(request_format, scopes: [:api, :read_api]) ||
find_user_from_feed_token(request_format) ||
find_user_from_static_object_token(request_format) ||
find_user_from_job_token_basic_auth ||
find_user_from_job_token_basic_auth_feature_flag_wrapper ||
find_user_from_job_token ||
find_user_from_personal_access_token_for_api_or_git ||
find_user_for_git_or_lfs_request
@ -81,6 +81,13 @@ module Gitlab
private
def find_user_from_job_token_basic_auth_feature_flag_wrapper
user = find_user_from_job_token_basic_auth
return if ::Feature.enabled?(:request_authenticator_exclude_job_token_basic_auth, user)
user
end
def access_token
strong_memoize(:access_token) do
super || find_personal_access_token_from_http_basic_auth

View File

@ -10533,6 +10533,9 @@ msgstr ""
msgid "CascadingSettings|Enforce for all subgroups"
msgstr ""
msgid "CascadingSettings|Lock tooltip icon"
msgstr ""
msgid "CascadingSettings|Setting cannot be changed"
msgstr ""
@ -16727,12 +16730,6 @@ msgstr ""
msgid "Dashboard"
msgstr ""
msgid "DashboardProjects|All"
msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
msgid "Dashboard|%{firstProject} and %{secondProject}"
msgstr ""

View File

@ -4,10 +4,6 @@ module QA
module Page
module Dashboard
class Projects < Page::Base
view 'app/views/shared/projects/_search_form.html.haml' do
element 'project-filter-form-container', required: true
end
view 'app/views/shared/projects/_project.html.haml' do
element 'project-content'
element 'user-access-role'
@ -35,9 +31,10 @@ module QA
end
def filter_by_name(name)
within_element('project-filter-form-container') do
fill_in :name, with: name
end
filter_input = find_element('filtered-search-term-input')
filter_input.click
filter_input.set(name)
click_element 'search-button'
end
def go_to_project(name)
@ -55,7 +52,7 @@ module QA
end
def clear_project_filter
fill_element('project-filter-form-container', "")
click_element 'filtered-search-clear-button'
end
private

View File

@ -189,7 +189,7 @@ function run_locally_or_in_container() {
local cmd=$1
local args=$2
local files=$3
local registry_url="registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.20-vale-3.4.2-markdownlint2-0.13.0-lychee-0.15.1"
local registry_url="registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.20-vale-3.6.1-markdownlint2-0.13.0-lychee-0.15.1"
if hash "${cmd}" 2>/dev/null
then

View File

@ -3,14 +3,18 @@
require 'spec_helper'
RSpec.describe 'Dashboard Archived Project', feature_category: :groups_and_projects do
let(:user) { create :user }
let(:project) { create :project }
let(:archived_project) { create(:project, :archived) }
let_it_be(:user) { create :user }
let_it_be(:project) { create :project }
let_it_be(:archived_project) { create(:project, :archived) }
let_it_be(:archived_project_2) { create(:project, :archived) }
before do
before_all do
project.add_maintainer(user)
archived_project.add_maintainer(user)
archived_project_2.add_maintainer(user)
end
before do
sign_in(user)
visit dashboard_projects_path
@ -21,32 +25,30 @@ RSpec.describe 'Dashboard Archived Project', feature_category: :groups_and_proje
expect(page).not_to have_link(archived_project.name)
end
it 'renders all projects' do
click_link 'Show archived projects'
expect(page).to have_link(project.name)
expect(page).to have_link(archived_project.name)
end
it 'renders only archived projects' do
click_link 'Show archived projects only'
click_link 'Inactive'
expect(page).to have_content(archived_project.name)
expect(page).not_to have_content(project.name)
end
it 'searches archived projects', :js do
click_button 'Name'
click_link 'Show archived projects'
click_link 'Inactive'
expect(page).to have_link(project.name)
expect(page).to have_link(archived_project.name)
expect(page).to have_link(archived_project_2.name)
fill_in 'project-filter-form-field', with: archived_project.name
search(archived_project.name)
find('#project-filter-form-field').native.send_keys :return
expect(page).not_to have_link(project.name)
expect(page).not_to have_link(archived_project_2.name)
expect(page).to have_link(archived_project.name)
end
def search(term)
filter_input = find_by_testid('filtered-search-term-input')
filter_input.click
filter_input.set(term)
click_button 'Search'
wait_for_requests
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Dashboard > User filters projects', feature_category: :groups_and_projects do
RSpec.describe 'Dashboard > User filters projects', :js, feature_category: :groups_and_projects do
let(:user) { create(:user) }
let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace, created_at: 2.seconds.ago, updated_at: 2.seconds.ago) }
let(:user2) { create(:user) }
@ -14,51 +14,58 @@ RSpec.describe 'Dashboard > User filters projects', feature_category: :groups_an
sign_in(user)
end
describe 'filtering personal projects' do
before do
project2.add_developer(user)
it 'allows viewing personal projects' do
project2.add_developer(user)
visit dashboard_projects_path
visit dashboard_projects_path
end
click_link 'Personal'
it 'filters by projects "Owned by me"' do
click_link 'Owned by me'
expect(page).to have_css('.is-active', text: 'Owned by me')
expect(page).to have_content('Victorialand')
expect(page).not_to have_content('Treasure')
end
expect(page).to have_content(project.name)
expect(page).not_to have_content(project2.name)
end
describe 'filtering starred projects', :js do
describe 'starred projects', :js do
before do
user.toggle_star(project)
visit dashboard_projects_path
end
it 'returns message when starred projects filter returns no results' do
fill_in 'project-filter-form-field', with: 'Beta\n'
expect(page).to have_content('There are no projects available to be displayed here')
expect(page).not_to have_content('You don\'t have starred projects yet')
end
end
describe 'without search bar', :js do
before do
it 'allows viewing starred projects' do
project2.add_developer(user)
visit dashboard_projects_path
click_link 'Starred'
expect(page).to have_content(project.name)
expect(page).not_to have_content(project2.name)
end
it 'autocompletes searches upon typing', :js do
expect(page).to have_content 'Victorialand'
expect(page).to have_content 'Treasure'
it 'shows empty state when starred projects filter returns no results' do
search('foo')
fill_in 'project-filter-form-field', with: 'Lord beerus\n'
expect(page).not_to have_content 'Victorialand'
expect(page).not_to have_content 'Treasure'
expect(page).not_to have_content("You don't have starred projects yet.")
end
end
it 'searches for projects' do
project2.add_developer(user)
visit dashboard_projects_path
expect(page).to have_content(project.name)
expect(page).to have_content(project2.name)
search(project.name)
expect(page).to have_content(project.name)
expect(page).not_to have_content(project2.name)
end
def search(term)
filter_input = find_by_testid('filtered-search-term-input')
filter_input.click
filter_input.set(term)
click_button 'Search'
wait_for_requests
end
end

View File

@ -19,15 +19,17 @@ RSpec.describe 'Projects > Settings > Packages', :js, feature_category: :package
let(:packages_enabled) { true }
it 'displays the packages access level setting' do
expect(page).to have_selector('[data-testid="package-registry-access-level"] > label', text: 'Package registry')
expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false)
expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true)
expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false)
expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true)
expect(page).to have_selector(
'input[name="project[project_feature_attributes][package_registry_access_level]"]',
visible: false
)
within_testid('package-registry-access-level') do
expect(page).to have_content('Package registry')
expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false)
expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true)
expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false)
expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true)
expect(page).to have_selector(
'input[name="project[project_feature_attributes][package_registry_access_level]"]',
visible: false
)
end
end
end

View File

@ -14,18 +14,20 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
find('button[data-testid=base-dropdown-toggle]')
end
shared_examples_for "sort order persists across all views" do |project_paths_label, vue_sort_label|
shared_examples_for "sort order persists across all views" do |sort_label|
it "is set on the dashboard_projects_path" do
visit(dashboard_projects_path)
expect(find('#sort-projects-dropdown')).to have_content(project_paths_label)
within '[data-testid=groups-projects-sort]' do
expect(find_dropdown_toggle).to have_content(sort_label)
end
end
it "is set on the explore_projects_path" do
visit(explore_projects_path)
within '[data-testid=groups-projects-sort]' do
expect(find_dropdown_toggle).to have_content(vue_sort_label)
expect(find_dropdown_toggle).to have_content(sort_label)
end
end
@ -33,7 +35,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
visit(group_canonical_path(group))
within '[data-testid=groups-projects-sort]' do
expect(find_dropdown_toggle).to have_content(vue_sort_label)
expect(find_dropdown_toggle).to have_content(sort_label)
end
end
@ -41,7 +43,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
visit(details_group_path(group))
within '[data-testid=groups-projects-sort]' do
expect(find_dropdown_toggle).to have_content(vue_sort_label)
expect(find_dropdown_toggle).to have_content(sort_label)
end
end
end
@ -57,18 +59,21 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
end
end
it_behaves_like "sort order persists across all views", 'Name', 'Name'
it_behaves_like "sort order persists across all views", 'Name'
end
context 'from dashboard projects', :js do
before do
sign_in(user)
visit(dashboard_projects_path)
find('#sort-projects-dropdown').click
first(:link, 'Name').click
within '[data-testid=groups-projects-sort]' do
find_dropdown_toggle.click
find('li', text: 'Created').click
wait_for_requests
end
end
it_behaves_like "sort order persists across all views", "Name", "Name"
it_behaves_like "sort order persists across all views", "Created"
end
context 'from group homepage', :js do
@ -82,7 +87,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
end
end
it_behaves_like "sort order persists across all views", "Oldest created", "Created"
it_behaves_like "sort order persists across all views", "Created"
end
context 'from group details', :js do
@ -96,6 +101,6 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
end
end
it_behaves_like "sort order persists across all views", "Oldest updated", "Updated"
it_behaves_like "sort order persists across all views", "Updated"
end
end

View File

@ -0,0 +1,89 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import { nextTick } from 'vue';
import CascadingLockIcon from '~/namespaces/cascading_settings/components/cascading_lock_icon.vue';
import LockTooltip from '~/namespaces/cascading_settings/components/lock_tooltip.vue';
describe('CascadingLockIcon', () => {
let wrapper;
const createComponent = (props = {}) => {
return shallowMount(CascadingLockIcon, {
propsData: {
isLockedByApplicationSettings: false,
isLockedByGroupAncestor: false,
...props,
},
});
};
const findLockTooltip = () => wrapper.findComponent(LockTooltip);
const findIcon = () => wrapper.findComponent(GlIcon);
beforeEach(() => {
wrapper = createComponent();
});
it('renders the GlIcon component', () => {
expect(findIcon().exists()).toBe(true);
});
it('sets correct attributes on GlIcon', () => {
wrapper = createComponent();
expect(findIcon().props()).toMatchObject({
name: 'lock',
ariaLabel: 'Lock tooltip icon',
});
});
it('does not render LockTooltip when targetElement is null', () => {
wrapper = createComponent();
expect(findLockTooltip().exists()).toBe(false);
});
it('renders LockTooltip after mounting', async () => {
wrapper = createComponent();
await nextTick();
await nextTick();
expect(findLockTooltip().exists()).toBe(true);
});
it('sets targetElement after mounting', async () => {
wrapper = createComponent();
await nextTick();
await nextTick();
expect(findLockTooltip().props().targetElement).not.toBeNull();
});
it('passes correct props to LockTooltip', async () => {
const ancestorNamespace = { path: '/test', fullName: 'Test' };
wrapper = createComponent({
ancestorNamespace,
isLockedByApplicationSettings: true,
isLockedByGroupAncestor: true,
});
await nextTick();
await nextTick();
expect(findLockTooltip().props()).toMatchObject({
ancestorNamespace,
isLockedByAdmin: true,
isLockedByGroupAncestor: true,
});
});
it('validates ancestorNamespace prop', () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// Valid prop
createComponent({ ancestorNamespace: { path: '/test', fullName: 'Test' } });
expect(consoleErrorSpy).not.toHaveBeenCalled();
// Invalid prop
createComponent({ ancestorNamespace: { path: '/test' } });
expect(consoleErrorSpy).toHaveBeenCalled();
consoleErrorSpy.mockRestore();
});
});

View File

@ -1,44 +1,44 @@
import { GlPopover } from '@gitlab/ui';
import { GlTooltip } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import HamlLockPopover from '~/namespaces/cascading_settings/components/haml_lock_popovers.vue';
import LockPopover from '~/namespaces/cascading_settings/components/lock_popover.vue';
import HamlLockTooltips from '~/namespaces/cascading_settings/components/haml_lock_tooltips.vue';
import LockTooltip from '~/namespaces/cascading_settings/components/lock_tooltip.vue';
describe('HamlLockPopover', () => {
describe('HamlLockTooltips', () => {
const mockNamespace = {
fullName: 'GitLab Org / GitLab',
path: '/gitlab-org/gitlab/-/edit',
};
const createPopoverMountEl = ({
const createTooltipMountEl = ({
lockedByApplicationSetting = false,
lockedByAncestor = false,
}) => {
const popoverMountEl = document.createElement('div');
popoverMountEl.classList.add('js-cascading-settings-lock-popover-target');
const tooltipMountEl = document.createElement('div');
tooltipMountEl.classList.add('js-cascading-settings-lock-tooltip-target');
const popoverData = {
const tooltipData = {
locked_by_application_setting: lockedByApplicationSetting,
locked_by_ancestor: lockedByAncestor,
};
popoverMountEl.dataset.popoverData = JSON.stringify(popoverData);
popoverMountEl.dataset.popoverData = JSON.stringify({
...popoverData,
tooltipMountEl.dataset.tooltipData = JSON.stringify(tooltipData);
tooltipMountEl.dataset.tooltipData = JSON.stringify({
...tooltipData,
ancestor_namespace: lockedByAncestor && !lockedByApplicationSetting ? mockNamespace : null,
});
document.body.appendChild(popoverMountEl);
document.body.appendChild(tooltipMountEl);
return popoverMountEl;
return tooltipMountEl;
};
let wrapper;
const createWrapper = () => {
wrapper = mountExtended(HamlLockPopover);
wrapper = mountExtended(HamlLockTooltips);
};
const findLockPopovers = () => wrapper.findAllComponents(LockPopover);
const findLockTooltips = () => wrapper.findAllComponents(LockTooltip);
afterEach(() => {
document.body.innerHTML = '';
@ -57,7 +57,7 @@ describe('HamlLockPopover', () => {
'when locked_by_application_setting is $lockedByApplicationSetting and locked_by_ancestor is $lockedByAncestor and ancestor_namespace is $ancestorNamespace',
({ ancestorNamespace, lockedByAncestor, lockedByApplicationSetting }) => {
beforeEach(() => {
domElement = createPopoverMountEl({
domElement = createTooltipMountEl({
ancestorNamespace,
lockedByApplicationSetting,
lockedByAncestor,
@ -66,40 +66,40 @@ describe('HamlLockPopover', () => {
});
it('locked_by_application_setting attribute', () => {
expect(findLockPopovers().at(0).props().isLockedByAdmin).toBe(lockedByApplicationSetting);
expect(findLockTooltips().at(0).props().isLockedByAdmin).toBe(lockedByApplicationSetting);
});
it('locked_by_ancestor attribute', () => {
expect(findLockPopovers().at(0).props().isLockedByGroupAncestor).toBe(lockedByAncestor);
expect(findLockTooltips().at(0).props().isLockedByGroupAncestor).toBe(lockedByAncestor);
});
it('ancestor_namespace attribute', () => {
expect(findLockPopovers().at(0).props().ancestorNamespace).toEqual(ancestorNamespace);
expect(findLockTooltips().at(0).props().ancestorNamespace).toEqual(ancestorNamespace);
});
it('target element', () => {
expect(findLockPopovers().at(0).props().targetElement).toBe(domElement);
expect(findLockTooltips().at(0).props().targetElement).toBe(domElement);
});
},
);
});
describe('when there are multiple mount elements', () => {
let popoverMountEl1;
let popoverMountEl2;
let tooltipMountEl1;
let tooltipMountEl2;
beforeEach(() => {
popoverMountEl1 = createPopoverMountEl({ lockedByApplicationSetting: true });
popoverMountEl2 = createPopoverMountEl({ lockedByAncestor: true });
tooltipMountEl1 = createTooltipMountEl({ lockedByApplicationSetting: true });
tooltipMountEl2 = createTooltipMountEl({ lockedByAncestor: true });
createWrapper();
});
it('mounts multiple popovers', () => {
const popovers = wrapper.findAllComponents(GlPopover).wrappers;
it('mounts multiple tooltips', () => {
const tooltips = wrapper.findAllComponents(GlTooltip).wrappers;
expect(popovers).toHaveLength(2);
expect(popovers[0].props('target')).toBe(popoverMountEl1);
expect(popovers[1].props('target')).toBe(popoverMountEl2);
expect(tooltips).toHaveLength(2);
expect(tooltips[0].props('target')).toBe(tooltipMountEl1);
expect(tooltips[1].props('target')).toBe(tooltipMountEl2);
});
});
});

View File

@ -1,8 +1,8 @@
import { GlLink, GlPopover, GlSprintf } from '@gitlab/ui';
import { GlLink, GlTooltip, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import LockPopover from '~/namespaces/cascading_settings/components/lock_popover.vue';
import LockTooltip from '~/namespaces/cascading_settings/components/lock_tooltip.vue';
describe('LockPopover', () => {
describe('LockTooltip', () => {
const mockNamespace = {
fullName: 'GitLab Org / GitLab',
path: '/gitlab-org/gitlab/-/edit',
@ -12,15 +12,15 @@ describe('LockPopover', () => {
'An administrator selected this setting for the instance and you cannot change it.';
let wrapper;
const popoverMountEl = document.createElement('div');
const tooltipMountEl = document.createElement('div');
const createWrapper = (props = {}) => {
wrapper = shallowMount(LockPopover, {
wrapper = shallowMount(LockTooltip, {
propsData: {
ancestorNamespace: mockNamespace,
isLockedByAdmin: false,
isLockedByGroupAncestor: true,
targetElement: popoverMountEl,
isLockedByGroupAncestor: false,
targetElement: tooltipMountEl,
...props,
},
stubs: {
@ -30,30 +30,33 @@ describe('LockPopover', () => {
};
const findLink = () => wrapper.findComponent(GlLink);
const findPopover = () => wrapper.findComponent(GlPopover);
const findTooltip = () => wrapper.findComponent(GlTooltip);
describe('when setting is locked by an admin setting', () => {
beforeEach(() => {
createWrapper({ isLockedByAdmin: true });
});
it('displays correct popover message', () => {
expect(findPopover().text()).toBe(applicationSettingMessage);
it('displays correct tooltip message', () => {
expect(findTooltip().text()).toBe(applicationSettingMessage);
});
it('sets `target` prop correctly', () => {
expect(findPopover().props().target).toBe(popoverMountEl);
expect(findTooltip().props().target).toBe(tooltipMountEl);
});
});
describe('when setting is locked by an ancestor namespace', () => {
describe('and ancestorNamespace is set', () => {
beforeEach(() => {
createWrapper({ isLockedByGroupAncestor: true, ancestorNamespace: mockNamespace });
createWrapper({
isLockedByGroupAncestor: true,
ancestorNamespace: mockNamespace,
});
});
it('displays correct popover message', () => {
expect(findPopover().text()).toBe(
it('displays correct tooltip message', () => {
expect(findTooltip().text()).toBe(
`This setting has been enforced by an owner of ${mockNamespace.fullName}.`,
);
});
@ -63,7 +66,7 @@ describe('LockPopover', () => {
});
it('sets `target` prop correctly', () => {
expect(findPopover().props().target).toBe(popoverMountEl);
expect(findTooltip().props().target).toBe(tooltipMountEl);
});
});
@ -73,7 +76,7 @@ describe('LockPopover', () => {
});
it('displays a generic message', () => {
expect(findPopover().text()).toBe(
expect(findTooltip().text()).toBe(
`This setting has been enforced by an owner and cannot be changed.`,
);
});
@ -82,15 +85,18 @@ describe('LockPopover', () => {
describe('when setting is locked by an application setting and an ancestor namespace', () => {
beforeEach(() => {
createWrapper({ isLockedByAdmin: true, isLockedByGroupAncestor: true });
createWrapper({
isLockedByAdmin: true,
isLockedByGroupAncestor: true,
});
});
it('displays correct popover message', () => {
expect(findPopover().text()).toBe(applicationSettingMessage);
it('displays correct tooltip message', () => {
expect(findTooltip().text()).toBe(applicationSettingMessage);
});
it('sets `target` prop correctly', () => {
expect(findPopover().props().target).toBe(popoverMountEl);
expect(findTooltip().props().target).toBe(tooltipMountEl);
});
});
@ -99,8 +105,8 @@ describe('LockPopover', () => {
createWrapper({ isLockedByAdmin: false, isLockedByGroupAncestor: false });
});
it('does not render popover', () => {
expect(findPopover().exists()).toBe(false);
it('does not render tooltip', () => {
expect(findTooltip().exists()).toBe(false);
});
});
});

View File

@ -1,22 +1,22 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlIcon } from '@gitlab/ui';
import projectSettingRow from '~/pages/projects/shared/permissions/components/project_setting_row.vue';
describe('Project Setting Row', () => {
let wrapper;
const mountComponent = (customProps = {}) => {
const createComponent = (customProps = {}) => {
const propsData = { ...customProps };
return shallowMount(projectSettingRow, { propsData });
};
beforeEach(() => {
wrapper = mountComponent();
wrapper = createComponent();
});
it('should show the label if it is set', async () => {
wrapper.setProps({ label: 'Test label' });
wrapper = createComponent({ label: 'Test label' });
await nextTick();
expect(wrapper.find('label').text()).toEqual('Test label');
@ -26,8 +26,24 @@ describe('Project Setting Row', () => {
expect(wrapper.find('label').exists()).toBe(false);
});
it('should apply gl-text-gray-400 class to label when locked', async () => {
wrapper = createComponent({ label: 'Test label', locked: true });
await nextTick();
expect(wrapper.find('label').classes()).toContain('gl-text-gray-400');
});
it('should render default slot content', () => {
wrapper = shallowMount(projectSettingRow, {
slots: {
'label-icon': GlIcon,
},
});
expect(wrapper.findComponent(GlIcon).exists()).toBe(true);
});
it('should show the help icon with the correct help path if it is set', async () => {
wrapper.setProps({ label: 'Test label', helpPath: '/123' });
wrapper = createComponent({ label: 'Test label', helpPath: '/123' });
await nextTick();
const link = wrapper.find('a');
@ -37,14 +53,14 @@ describe('Project Setting Row', () => {
});
it('should hide the help icon if no help path is set', async () => {
wrapper.setProps({ label: 'Test label' });
wrapper = createComponent({ label: 'Test label' });
await nextTick();
expect(wrapper.find('a').exists()).toBe(false);
});
it('should show the help text if it is set', async () => {
wrapper.setProps({ helpText: 'Test text' });
wrapper = createComponent({ helpText: 'Test text' });
await nextTick();
expect(wrapper.find('span').text()).toEqual('Test text');

View File

@ -8,10 +8,10 @@ import {
SORT_OPTIONS,
SORT_DIRECTION_ASC,
SORT_DIRECTION_DESC,
} from '~/projects/explore/constants';
} from '~/projects/filtered_search_and_sort/constants';
import { RECENT_SEARCHES_STORAGE_KEY_PROJECTS } from '~/filtered_search/recent_searches_storage_keys';
import FilteredSearchAndSort from '~/groups_projects/components/filtered_search_and_sort.vue';
import ProjectsExploreFilteredSearchAndSort from '~/projects/explore/components/filtered_search_and_sort.vue';
import ProjectsExploreFilteredSearchAndSort from '~/projects/filtered_search_and_sort/components/filtered_search_and_sort.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { visitUrl } from '~/lib/utils/url_utility';
@ -37,8 +37,9 @@ describe('ProjectsExploreFilteredSearchAndSort', () => {
{ id: 6, name: 'Ruby', color: '#701516', created_at: '2023-09-19T14:42:01.493Z' },
{ id: 11, name: 'Shell', color: '#89e051', created_at: '2023-09-19T14:42:11.923Z' },
],
starredExploreProjectsPath: '/explore/projects/starred',
exploreRootPath: '/explore',
pathsToExcludeSortOn: ['/explore/projects/starred', '/explore'],
sortEventName: 'use_sort_projects_explore',
filterEventName: 'use_filter_bar_projects_explore',
};
const createComponent = ({
@ -124,7 +125,7 @@ describe('ProjectsExploreFilteredSearchAndSort', () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
expect(trackEventSpy).toHaveBeenCalledWith(
'use_filter_bar_projects_explore',
defaultProvide.filterEventName,
{
label: JSON.stringify({ search: searchTerm, language: 'CSS' }),
},
@ -152,7 +153,7 @@ describe('ProjectsExploreFilteredSearchAndSort', () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
expect(trackEventSpy).toHaveBeenCalledWith(
'use_sort_projects_explore',
defaultProvide.sortEventName,
{
label: `${SORT_OPTION_UPDATED.value}_${SORT_DIRECTION_ASC}`,
},
@ -180,7 +181,7 @@ describe('ProjectsExploreFilteredSearchAndSort', () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
expect(trackEventSpy).toHaveBeenCalledWith(
'use_sort_projects_explore',
defaultProvide.sortEventName,
{
label: `${SORT_OPTION_CREATED.value}_${SORT_DIRECTION_DESC}`,
},
@ -190,13 +191,10 @@ describe('ProjectsExploreFilteredSearchAndSort', () => {
});
describe('when on the "Most starred" tab', () => {
it.each([defaultProvide.starredExploreProjectsPath, defaultProvide.exploreRootPath])(
'does not show sort dropdown',
(pathname) => {
createComponent({ pathname });
it.each(defaultProvide.pathsToExcludeSortOn)('does not show sort dropdown', (pathname) => {
createComponent({ pathname });
expect(findFilteredSearchAndSort().props('sortOptions')).toEqual([]);
},
);
expect(findFilteredSearchAndSort().props('sortOptions')).toEqual([]);
});
});
});

View File

@ -42,11 +42,11 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do
user_group.add_owner(user)
end
describe '#cascading_namespace_settings_popover_data' do
describe '#cascading_namespace_settings_tooltip_data' do
attribute = :math_rendering_limits_enabled
subject do
helper.cascading_namespace_settings_popover_data(
helper.cascading_namespace_settings_tooltip_data(
attribute,
subgroup1,
->(locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') }
@ -61,7 +61,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do
it 'returns expected hash' do
expect(subject).to match({
popover_data: {
tooltip_data: {
locked_by_application_setting: true,
locked_by_ancestor: false
}.to_json,
@ -79,7 +79,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do
it 'returns expected hash' do
expect(subject).to match({
popover_data: {
tooltip_data: {
locked_by_application_setting: false,
locked_by_ancestor: true,
ancestor_namespace: {

View File

@ -434,6 +434,10 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
it 'returns false when there are no projects and there is no name' do
expect(helper.show_projects?(Project.none, {})).to eq(false)
end
it 'returns true when there are no projects but archived param is "only"' do
expect(helper.show_projects?(Project.none, archived: 'only')).to eq(true)
end
end
describe '#push_to_create_project_command' do
@ -1893,14 +1897,13 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
end
end
describe '#projects_explore_filtered_search_and_sort_app_data' do
describe '#projects_filtered_search_and_sort_app_data' do
it 'returns expected json' do
expect(Gitlab::Json.parse(helper.projects_explore_filtered_search_and_sort_app_data)).to eq(
expect(Gitlab::Json.parse(helper.projects_filtered_search_and_sort_app_data)).to eq(
{
'initial_sort' => 'created_desc',
'programming_languages' => ProgrammingLanguage.most_popular,
'starred_explore_projects_path' => starred_explore_projects_path,
'explore_root_path' => explore_root_path
'paths_to_exclude_sort_on' => [starred_explore_projects_path, explore_root_path]
}
)
end

View File

@ -363,6 +363,32 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
end
end
describe '#find_user_from_job_token_basic_auth' do
let_it_be(:user) { create(:user) }
let_it_be(:job) { create(:ci_build, user: user, status: :running) }
before do
allow(subject).to receive(:has_basic_credentials?).and_return(true)
allow(subject).to receive(:user_name_and_password).and_return([::Gitlab::Auth::CI_JOB_USER, job.token])
end
context 'when feature flag request_authenticator_exclude_job_token_basic_auth is enabled' do
it 'does not find a user' do
expect(subject.user([:api])).to eq nil
end
end
context 'when feature flag request_authenticator_exclude_job_token_basic_auth is disabled' do
before do
stub_feature_flags(request_authenticator_exclude_job_token_basic_auth: false)
end
it 'finds a job token user' do
expect(subject.user([:api])).to eq user
end
end
end
describe '#find_user_from_job_token' do
let_it_be(:user) { build(:user) }
let_it_be(:job) { build(:ci_build, user: user, status: :running) }

View File

@ -30,14 +30,14 @@ RSpec.shared_examples 'a cascading setting' do
expect(page).not_to have_selector '[data-testid="enforce-for-all-subgroups-checkbox"]'
end
it 'displays lock icon with popover', :js do
it 'displays lock icon with tooltip', :js do
visit subgroup_path
page.within form_group_selector do
find('[data-testid="cascading-settings-lock-icon"]').click
end
page.within '[data-testid="cascading-settings-lock-popover"]' do
page.within '[data-testid="cascading-settings-lock-tooltip"]' do
expect(page).to have_text 'This setting has been enforced by an owner of Foo bar.'
expect(page).to have_link 'Foo bar', href: setting_path
end

View File

@ -443,4 +443,38 @@ RSpec.describe Tooling::Danger::AnalyticsInstrumentation, feature_category: :ser
end
end
end
describe '#warn_about_migrated_redis_keys_specs!' do
let(:redis_hll_file) { 'lib/gitlab/usage_data_counters/hll_redis_key_overrides.yml' }
let(:total_counter_file) { 'lib/gitlab/usage_data_counters/total_counter_redis_key_overrides.yml' }
subject(:check_redis_keys_files_overrides) { analytics_instrumentation.warn_about_migrated_redis_keys_specs! }
before do
allow(fake_helper).to receive(:changed_lines).with(redis_hll_file).and_return([file_diff_hll])
allow(fake_helper).to receive(:changed_lines).with(total_counter_file).and_return([file_diff_total])
end
context 'when new keys added to overrides files' do
let(:file_diff_hll) { "+user_viewed_cluster_configuration-user: user_viewed_cluster_configuration" }
let(:file_diff_total) { "+user_viewed_cluster_configuration-user: USER_VIEWED_CLUSTER_CONFIGURATION" }
it 'adds suggestion to add specs' do
expect(analytics_instrumentation).to receive(:warn)
check_redis_keys_files_overrides
end
end
context 'when no new keys added to overrides files' do
let(:file_diff_hll) { "-user_viewed_cluster_configuration-user: user_viewed_cluster_configuration" }
let(:file_diff_total) { "-user_viewed_cluster_configuration-user: USER_VIEWED_CLUSTER_CONFIGURATION" }
it 'adds suggestion to add specs' do
expect(analytics_instrumentation).not_to receive(:warn)
check_redis_keys_files_overrides
end
end
end
end

View File

@ -1,19 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'dashboard/projects/_nav.html.haml' do
it 'highlights All tab by default' do
render
expect(rendered).to have_css('a.active', text: 'All')
end
it 'highlights Personal tab personal param is present' do
controller.params[:personal] = true
render
expect(rendered).to have_css('a.active', text: 'Personal')
end
end

View File

@ -97,12 +97,22 @@ module Tooling
end
end
def modified_config_files
helper.modified_files.select { |f| f.include?('config/metrics') && f.end_with?('yml') }
def warn_about_migrated_redis_keys_specs!
override_files_changes = ["lib/gitlab/usage_data_counters/hll_redis_key_overrides.yml",
"lib/gitlab/usage_data_counters/total_counter_redis_key_overrides.yml"].map do |filename|
helper.changed_lines(filename).filter { |line| line.start_with?("+") }
end
return if override_files_changes.flatten.none?
warn "Redis keys overrides were added. Please consider cover keys merging with specs. See the [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/475191) for details"
end
private
def modified_config_files
helper.modified_files.select { |f| f.include?('config/metrics') && f.end_with?('yml') }
end
def comment_removed_metric(filename, has_removed_url, has_removed_milestone)
mr_has_milestone = !helper.mr_milestone.nil?
milestone = mr_has_milestone ? helper.mr_milestone['title'] : '[PLEASE SET MILESTONE]'