Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-23 06:09:40 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-23 06:09:40 +0300
commitd66f7a4222f48b51c468c3e0b4f1824c9457345e (patch)
treee6fe58e902027e003d90d4fe5ffb0697b5d1c483
parent814fd46dfdf3493c06007653774327ddb2f02938 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue141
-rw-r--r--app/assets/javascripts/issues_list/constants.js69
-rw-r--r--app/assets/javascripts/issues_list/index.js8
-rw-r--r--app/assets/javascripts/issues_list/utils.js74
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/models/project_services/ewm_service.rb9
-rw-r--r--changelogs/unreleased/msj-ewm-integration.yml5
-rw-r--r--doc/administration/geo/replication/docker_registry.md2
-rw-r--r--doc/administration/gitaly/configure_gitaly.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md2
-rw-r--r--doc/administration/reference_architectures/10k_users.md2
-rw-r--r--doc/api/runners.md2
-rw-r--r--doc/ci/services/gitlab.md2
-rw-r--r--doc/development/cascading_settings.md36
-rw-r--r--doc/development/elasticsearch.md4
-rw-r--r--doc/development/fe_guide/vue.md6
-rw-r--r--doc/development/graphql_guide/authorization.md2
-rw-r--r--doc/development/pipelines.md4
-rw-r--r--doc/development/testing_guide/frontend_testing.md4
-rw-r--r--doc/install/aws/index.md4
-rw-r--r--doc/operations/incident_management/integrations.md2
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md2
-rw-r--r--doc/update/restore_after_failure.md2
-rw-r--r--doc/user/application_security/api_fuzzing/index.md4
-rw-r--r--doc/user/application_security/dast/index.md6
-rw-r--r--doc/user/application_security/sast/analyzers.md2
-rw-r--r--doc/user/compliance/license_compliance/index.md2
-rw-r--r--doc/user/project/import/github.md2
-rw-r--r--doc/user/project/integrations/ewm.md46
-rw-r--r--doc/user/project/time_tracking.md4
-rw-r--r--locale/gitlab.pot5
-rw-r--r--spec/frontend/issues_list/components/issues_list_app_spec.js73
-rw-r--r--spec/frontend/issues_list/mock_data.js26
-rw-r--r--spec/frontend/issues_list/utils_spec.js54
-rw-r--r--spec/helpers/issues_helper_spec.rb4
35 files changed, 472 insertions, 144 deletions
diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue
index 57c5107fcbb..4b7b12f6b99 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlEmptyState, GlIcon, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { toNumber } from 'lodash';
import createFlash from '~/flash';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
@@ -7,50 +8,35 @@ import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import {
CREATED_DESC,
+ i18n,
+ MAX_LIST_SIZE,
PAGE_SIZE,
RELATIVE_POSITION_ASC,
sortOptions,
sortParams,
} from '~/issues_list/constants';
+import {
+ convertToApiParams,
+ convertToSearchQuery,
+ convertToUrlParams,
+ getFilterTokens,
+ getSortKey,
+} from '~/issues_list/utils';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/common_utils';
-import { __, s__ } from '~/locale';
+import { __ } from '~/locale';
+import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
+import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import eventHub from '../eventhub';
import IssueCardTimeInfo from './issue_card_time_info.vue';
export default {
CREATED_DESC,
+ i18n,
IssuableListTabs,
PAGE_SIZE,
sortOptions,
sortParams,
- i18n: {
- calendarLabel: __('Subscribe to calendar'),
- jiraIntegrationMessage: s__(
- 'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.',
- ),
- jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'),
- jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'),
- newIssueLabel: __('New issue'),
- noClosedIssuesTitle: __('There are no closed issues'),
- noOpenIssuesDescription: __('To keep this project going, create a new issue'),
- noOpenIssuesTitle: __('There are no open issues'),
- noIssuesSignedInDescription: __(
- 'Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.',
- ),
- noIssuesSignedInTitle: __(
- 'The Issue Tracker is the place to add things that need to be improved or solved in a project',
- ),
- noIssuesSignedOutButtonText: __('Register / Sign In'),
- noIssuesSignedOutDescription: __(
- 'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.',
- ),
- noIssuesSignedOutTitle: __('There are no issues to show'),
- noSearchResultsDescription: __('To widen your search, change or remove filters above'),
- noSearchResultsTitle: __('Sorry, your filter produced no results'),
- reorderError: __('An error occurred while reordering issues.'),
- rssLabel: __('Subscribe to RSS feed'),
- },
components: {
CsvImportExportButtons,
GlButton,
@@ -66,6 +52,9 @@ export default {
GlTooltip: GlTooltipDirective,
},
inject: {
+ autocompleteUsersPath: {
+ default: '',
+ },
calendarPath: {
default: '',
},
@@ -81,9 +70,6 @@ export default {
exportCsvPath: {
default: '',
},
- fullPath: {
- default: '',
- },
hasIssues: {
default: false,
},
@@ -99,6 +85,12 @@ export default {
newIssuePath: {
default: '',
},
+ projectLabelsPath: {
+ default: '',
+ },
+ projectPath: {
+ default: '',
+ },
rssPath: {
default: '',
},
@@ -112,27 +104,15 @@ export default {
data() {
const orderBy = getParameterByName('order_by');
const sort = getParameterByName('sort');
- const sortKey = Object.keys(sortParams).find(
- (key) => sortParams[key].order_by === orderBy && sortParams[key].sort === sort,
- );
-
- const search = getParameterByName('search') || '';
- const tokens = search.split(' ').map((searchWord) => ({
- type: 'filtered-search-term',
- value: {
- data: searchWord,
- },
- }));
return {
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
- filters: sortParams[sortKey] || {},
- filterTokens: tokens,
+ filterTokens: getFilterTokens(window.location.search),
isLoading: false,
issues: [],
page: toNumber(getParameterByName('page')) || 1,
showBulkEditSidebar: false,
- sortKey: sortKey || CREATED_DESC,
+ sortKey: getSortKey(orderBy, sort) || CREATED_DESC,
state: getParameterByName('state') || IssuableStates.Opened,
totalIssues: 0,
};
@@ -144,13 +124,46 @@ export default {
isOpenTab() {
return this.state === IssuableStates.Opened;
},
+ apiFilterParams() {
+ return convertToApiParams(this.filterTokens);
+ },
+ urlFilterParams() {
+ return convertToUrlParams(this.filterTokens);
+ },
searchQuery() {
- return (
- this.filterTokens
- .map((searchTerm) => searchTerm.value.data)
- .filter((searchWord) => Boolean(searchWord))
- .join(' ') || undefined
- );
+ return convertToSearchQuery(this.filterTokens) || undefined;
+ },
+ searchTokens() {
+ return [
+ {
+ type: 'author_username',
+ title: __('Author'),
+ icon: 'pencil',
+ token: AuthorToken,
+ dataType: 'user',
+ unique: true,
+ defaultAuthors: [],
+ fetchAuthors: this.fetchUsers,
+ },
+ {
+ type: 'assignee_username',
+ title: __('Assignee'),
+ icon: 'user',
+ token: AuthorToken,
+ dataType: 'user',
+ unique: true,
+ defaultAuthors: [],
+ fetchAuthors: this.fetchUsers,
+ },
+ {
+ type: 'labels',
+ title: __('Label'),
+ icon: 'labels',
+ token: LabelToken,
+ defaultLabels: [],
+ fetchLabels: this.fetchLabels,
+ },
+ ];
},
showPaginationControls() {
return this.issues.length > 0;
@@ -169,7 +182,8 @@ export default {
page: this.page,
search: this.searchQuery,
state: this.state,
- ...this.filters,
+ ...sortParams[this.sortKey],
+ ...this.urlFilterParams,
};
},
},
@@ -184,6 +198,21 @@ export default {
eventHub.$off('issuables:toggleBulkEdit');
},
methods: {
+ fetchLabels(search) {
+ if (this.labelsCache) {
+ return search
+ ? Promise.resolve(fuzzaldrinPlus.filter(this.labelsCache, search, { key: 'title' }))
+ : Promise.resolve(this.labelsCache.slice(0, MAX_LIST_SIZE));
+ }
+
+ return axios.get(this.projectLabelsPath).then(({ data }) => {
+ this.labelsCache = data;
+ return data.slice(0, MAX_LIST_SIZE);
+ });
+ },
+ fetchUsers(search) {
+ return axios.get(this.autocompleteUsersPath, { params: { search } });
+ },
fetchIssues() {
if (!this.hasIssues) {
return undefined;
@@ -199,7 +228,8 @@ export default {
search: this.searchQuery,
state: this.state,
with_labels_details: true,
- ...this.filters,
+ ...sortParams[this.sortKey],
+ ...this.apiFilterParams,
},
})
.then(({ data, headers }) => {
@@ -278,7 +308,6 @@ export default {
},
handleSort(value) {
this.sortKey = value;
- this.filters = sortParams[value];
this.fetchIssues();
},
},
@@ -288,10 +317,10 @@ export default {
<template>
<issuable-list
v-if="hasIssues"
- :namespace="fullPath"
+ :namespace="projectPath"
recent-searches-storage-key="issues"
:search-input-placeholder="__('Search or filter results…')"
- :search-tokens="[]"
+ :search-tokens="searchTokens"
:initial-filter-value="filterTokens"
:sort-options="$options.sortOptions"
:initial-sort-by="sortKey"
diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js
index f6f23af80ba..4a460859201 100644
--- a/app/assets/javascripts/issues_list/constants.js
+++ b/app/assets/javascripts/issues_list/constants.js
@@ -1,4 +1,4 @@
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
// Maps sort order as it appears in the URL query to API `order_by` and `sort` params.
const PRIORITY = 'priority';
@@ -53,6 +53,34 @@ export const availableSortOptionsJira = [
},
];
+export const i18n = {
+ calendarLabel: __('Subscribe to calendar'),
+ jiraIntegrationMessage: s__(
+ 'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.',
+ ),
+ jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'),
+ jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'),
+ newIssueLabel: __('New issue'),
+ noClosedIssuesTitle: __('There are no closed issues'),
+ noOpenIssuesDescription: __('To keep this project going, create a new issue'),
+ noOpenIssuesTitle: __('There are no open issues'),
+ noIssuesSignedInDescription: __(
+ 'Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.',
+ ),
+ noIssuesSignedInTitle: __(
+ 'The Issue Tracker is the place to add things that need to be improved or solved in a project',
+ ),
+ noIssuesSignedOutButtonText: __('Register / Sign In'),
+ noIssuesSignedOutDescription: __(
+ 'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.',
+ ),
+ noIssuesSignedOutTitle: __('There are no issues to show'),
+ noSearchResultsDescription: __('To widen your search, change or remove filters above'),
+ noSearchResultsTitle: __('Sorry, your filter produced no results'),
+ reorderError: __('An error occurred while reordering issues.'),
+ rssLabel: __('Subscribe to RSS feed'),
+};
+
export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map';
export const BLOCKING_ISSUES_ASC = 'BLOCKING_ISSUES_ASC';
@@ -242,3 +270,42 @@ export const sortOptions = [
},
},
];
+
+export const MAX_LIST_SIZE = 10;
+
+export const FILTERED_SEARCH_TERM = 'filtered-search-term';
+export const OPERATOR_IS = '=';
+export const OPERATOR_IS_NOT = '!=';
+
+export const filters = {
+ author_username: {
+ apiParam: {
+ [OPERATOR_IS]: 'author_username',
+ [OPERATOR_IS_NOT]: 'not[author_username]',
+ },
+ urlParam: {
+ [OPERATOR_IS]: 'author_username',
+ [OPERATOR_IS_NOT]: 'not[author_username]',
+ },
+ },
+ assignee_username: {
+ apiParam: {
+ [OPERATOR_IS]: 'assignee_username',
+ [OPERATOR_IS_NOT]: 'not[assignee_username]',
+ },
+ urlParam: {
+ [OPERATOR_IS]: 'assignee_username[]',
+ [OPERATOR_IS_NOT]: 'not[assignee_username][]',
+ },
+ },
+ labels: {
+ apiParam: {
+ [OPERATOR_IS]: 'labels',
+ [OPERATOR_IS_NOT]: 'not[labels]',
+ },
+ urlParam: {
+ [OPERATOR_IS]: 'label_name[]',
+ [OPERATOR_IS_NOT]: 'not[label_name][]',
+ },
+ },
+};
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js
index 0b64df50691..85b64be7718 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues_list/index.js
@@ -73,6 +73,7 @@ export function initIssuesListApp() {
}
const {
+ autocompleteUsersPath,
calendarPath,
canBulkUpdate,
canEdit,
@@ -81,7 +82,6 @@ export function initIssuesListApp() {
emptyStateSvgPath,
endpoint,
exportCsvPath,
- fullPath,
hasBlockedIssuesFeature,
hasIssuableHealthStatusFeature,
hasIssues,
@@ -93,6 +93,8 @@ export function initIssuesListApp() {
maxAttachmentSize,
newIssuePath,
projectImportJiraPath,
+ projectLabelsPath,
+ projectPath,
rssPath,
showNewIssueLink,
signInPath,
@@ -104,11 +106,11 @@ export function initIssuesListApp() {
// issue is fixed upstream in https://github.com/vuejs/vue-apollo/pull/1153
apolloProvider: {},
provide: {
+ autocompleteUsersPath,
calendarPath,
canBulkUpdate: parseBoolean(canBulkUpdate),
emptyStateSvgPath,
endpoint,
- fullPath,
hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
hasIssues: parseBoolean(hasIssues),
@@ -117,6 +119,8 @@ export function initIssuesListApp() {
issuesPath,
jiraIntegrationPath,
newIssuePath,
+ projectLabelsPath,
+ projectPath,
rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink),
signInPath,
diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js
new file mode 100644
index 00000000000..f470ccb5e06
--- /dev/null
+++ b/app/assets/javascripts/issues_list/utils.js
@@ -0,0 +1,74 @@
+import { FILTERED_SEARCH_TERM, filters, sortParams } from '~/issues_list/constants';
+
+export const getSortKey = (orderBy, sort) =>
+ Object.keys(sortParams).find(
+ (key) => sortParams[key].order_by === orderBy && sortParams[key].sort === sort,
+ );
+
+const tokenTypes = Object.keys(filters);
+
+const urlParamKeys = tokenTypes.flatMap((key) => Object.values(filters[key].urlParam));
+
+const getTokenTypeFromUrlParamKey = (urlParamKey) =>
+ tokenTypes.find((key) => Object.values(filters[key].urlParam).includes(urlParamKey));
+
+const getOperatorFromUrlParamKey = (tokenType, urlParamKey) =>
+ Object.entries(filters[tokenType].urlParam).find(([, urlParam]) => urlParam === urlParamKey)[0];
+
+const convertToFilteredTokens = (locationSearch) =>
+ Array.from(new URLSearchParams(locationSearch).entries())
+ .filter(([key]) => urlParamKeys.includes(key))
+ .map(([key, data]) => {
+ const type = getTokenTypeFromUrlParamKey(key);
+ const operator = getOperatorFromUrlParamKey(type, key);
+ return {
+ type,
+ value: { data, operator },
+ };
+ });
+
+const convertToFilteredSearchTerms = (locationSearch) =>
+ new URLSearchParams(locationSearch)
+ .get('search')
+ ?.split(' ')
+ .map((word) => ({
+ type: FILTERED_SEARCH_TERM,
+ value: {
+ data: word,
+ },
+ })) || [];
+
+export const getFilterTokens = (locationSearch) => {
+ if (!locationSearch) {
+ return [];
+ }
+ const filterTokens = convertToFilteredTokens(locationSearch);
+ const searchTokens = convertToFilteredSearchTerms(locationSearch);
+ return filterTokens.concat(searchTokens);
+};
+
+export const convertToApiParams = (filterTokens) =>
+ filterTokens
+ .filter((token) => token.type !== FILTERED_SEARCH_TERM)
+ .reduce((acc, token) => {
+ const apiParam = filters[token.type].apiParam[token.value.operator];
+ return Object.assign(acc, {
+ [apiParam]: acc[apiParam] ? `${acc[apiParam]},${token.value.data}` : token.value.data,
+ });
+ }, {});
+
+export const convertToUrlParams = (filterTokens) =>
+ filterTokens
+ .filter((token) => token.type !== FILTERED_SEARCH_TERM)
+ .reduce((acc, token) => {
+ const urlParam = filters[token.type].urlParam[token.value.operator];
+ return Object.assign(acc, {
+ [urlParam]: acc[urlParam] ? acc[urlParam].concat(token.value.data) : [token.value.data],
+ });
+ }, {});
+
+export const convertToSearchQuery = (filterTokens) =>
+ filterTokens
+ .filter((token) => token.type === FILTERED_SEARCH_TERM && token.value.data)
+ .map((token) => token.value.data)
+ .join(' ');
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 0a83e707412..2296fe91fba 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -165,6 +165,7 @@ module IssuesHelper
def issues_list_data(project, current_user, finder)
{
+ autocomplete_users_path: autocomplete_users_path(active: true, current_user: true, project_id: project.id, format: :json),
calendar_path: url_for(safe_params.merge(calendar_url_options)),
can_bulk_update: can?(current_user, :admin_issue, project).to_s,
can_edit: can?(current_user, :admin_project, project).to_s,
@@ -173,7 +174,6 @@ module IssuesHelper
empty_state_svg_path: image_path('illustrations/issues.svg'),
endpoint: expose_path(api_v4_projects_issues_path(id: project.id)),
export_csv_path: export_csv_project_issues_path(project),
- full_path: project.full_path,
has_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: import_csv_namespace_project_issues_path,
is_signed_in: current_user.present?.to_s,
@@ -182,6 +182,8 @@ module IssuesHelper
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project),
+ project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
+ project_path: project.full_path,
rss_path: url_for(safe_params.merge(rss_url_options)),
show_new_issue_link: show_new_issue_link?(project).to_s,
sign_in_path: new_user_session_path
diff --git a/app/models/project_services/ewm_service.rb b/app/models/project_services/ewm_service.rb
index af402e50292..90fcbb10d2b 100644
--- a/app/models/project_services/ewm_service.rb
+++ b/app/models/project_services/ewm_service.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class EwmService < IssueTrackerService
+ include ActionView::Helpers::UrlHelper
+
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def self.reference_pattern(only_long: true)
@@ -12,7 +14,12 @@ class EwmService < IssueTrackerService
end
def description
- s_('IssueTracker|EWM work items tracker')
+ s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
+ end
+
+ def help
+ docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/ewm'), target: '_blank', rel: 'noopener noreferrer'
+ s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
diff --git a/changelogs/unreleased/msj-ewm-integration.yml b/changelogs/unreleased/msj-ewm-integration.yml
new file mode 100644
index 00000000000..2773510a012
--- /dev/null
+++ b/changelogs/unreleased/msj-ewm-integration.yml
@@ -0,0 +1,5 @@
+---
+title: Review and revise EWM integration UI text
+merge_request: 59386
+author:
+type: other
diff --git a/doc/administration/geo/replication/docker_registry.md b/doc/administration/geo/replication/docker_registry.md
index ea73614511f..52855f1912b 100644
--- a/doc/administration/geo/replication/docker_registry.md
+++ b/doc/administration/geo/replication/docker_registry.md
@@ -99,7 +99,7 @@ pair for all the sites. The **secondary** site will use this key to
generate a short-lived JWT that is pull-only-capable to access the
**primary** site Container Registry.
-For each application and Sidekiq node on the **secondary** site:
+For each application and Sidekiq node on the **secondary** site:
1. SSH into the node and login as the `root` user:
diff --git a/doc/administration/gitaly/configure_gitaly.md b/doc/administration/gitaly/configure_gitaly.md
index 51ad376a625..b8fce37d83d 100644
--- a/doc/administration/gitaly/configure_gitaly.md
+++ b/doc/administration/gitaly/configure_gitaly.md
@@ -979,7 +979,7 @@ identical fetches. It:
The pack-objects cache is a local cache. It:
- Stores its metadata in the memory of the Gitaly process it is enabled in.
-- Stores the actual Git data it is caching in files on local storage.
+- Stores the actual Git data it is caching in files on local storage.
Using local files has the benefit that the operating system may
automatically keep parts of the pack-objects cache files in RAM,
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 9f87192aab0..ebb29b5c70e 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -254,7 +254,7 @@ The following metrics are available:
|:--------------------------------- |:--------- |:------------------------------------------------------------- |:-------------------------------------- |:--------------------------------------------------------- |
| `db_load_balancing_hosts` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/13630) | Current number of load balancing hosts | |
| `sidekiq_load_balancing_count` | Counter | 13.11 | Sidekiq jobs using load balancing with data consistency set to :sticky or :delayed | `queue`, `boundary`, `external_dependencies`, `feature_category`, `job_status`, `urgency`, `data_consistency`, `database_chosen` |
-
+
## Database partitioning metrics **(PREMIUM SELF)**
The following metrics are available:
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index 97af1fe8d3c..a6e5e4e00ed 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -2363,7 +2363,7 @@ considered and customer technical support will be considered out of scope.
As an alternative approach, you can also run select components of GitLab as Cloud Native
in Kubernetes via our official [Helm Charts](https://docs.gitlab.com/charts/).
In this setup, we support running the equivalent of GitLab Rails and Sidekiq nodes
-in a Kubernetes cluster, named Webservice and Sidekiq respectively. In addition,
+in a Kubernetes cluster, named Webservice and Sidekiq respectively. In addition,
the following other supporting services are supported: NGINX, Task Runner, Migrations,
Prometheus and Grafana.
diff --git a/doc/api/runners.md b/doc/api/runners.md
index c96ff1b0360..f9febaef305 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -156,7 +156,7 @@ Example response:
Get details of a runner.
-[Maintainer access or higher](../user/permissions.md) is required to get runner details at the project and group level.
+[Maintainer access or higher](../user/permissions.md) is required to get runner details at the project and group level.
Instance-level runner details via this endpoint are available to all signed in users.
diff --git a/doc/ci/services/gitlab.md b/doc/ci/services/gitlab.md
index 8a582cc87eb..a0e15b4e960 100644
--- a/doc/ci/services/gitlab.md
+++ b/doc/ci/services/gitlab.md
@@ -28,7 +28,7 @@ tests access to the GitLab API.
[assign them to a variable in the user interface](../variables/README.md#project-cicd-variables).
Then assign that variable to the corresponding variable in your
`.gitlab-ci.yml` file.
-
+
Then, commands in `script:` sections in your `.gitlab-ci.yml` file can access the API at `http://gitlab/api/v4`.
For more information about why `gitlab` is used for the `Host`, see
diff --git a/doc/development/cascading_settings.md b/doc/development/cascading_settings.md
index 9bf21a03163..b01cf9cb8d1 100644
--- a/doc/development/cascading_settings.md
+++ b/doc/development/cascading_settings.md
@@ -8,10 +8,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> Introduced in [GitLab 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/321724).
-The cascading settings framework allows groups to essentially inherit settings
-values from ancestors (parent group on up the group hierarchy) and from
+The cascading settings framework allows groups to essentially inherit settings
+values from ancestors (parent group on up the group hierarchy) and from
instance-level application settings. The framework also allows settings values
-to be enforced on groups lower in the hierarchy.
+to be enforced on groups lower in the hierarchy.
Cascading settings can currently only be defined within `NamespaceSetting`, though
the framework may be extended to other objects in the future.
@@ -22,7 +22,7 @@ Settings are not cascading by default. To define a cascading setting, take the f
1. In the `NamespaceSetting` model, define the new attribute using the `cascading_attr`
helper method. You can use an array to define multiple attributes on a single line.
-
+
```ruby
class NamespaceSetting
include CascadingNamespaceSettingAttribute
@@ -32,11 +32,11 @@ Settings are not cascading by default. To define a cascading setting, take the f
```
1. Create the database columns.
-
- You can use the following database migration helper for a completely new setting.
- The helper creates four columns, two each in `namespace_settings` and
+
+ You can use the following database migration helper for a completely new setting.
+ The helper creates four columns, two each in `namespace_settings` and
`application_settings`.
-
+
```ruby
class AddDelayedProjectRemovalCascadingSetting < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings
@@ -50,23 +50,23 @@ Settings are not cascading by default. To define a cascading setting, take the f
end
end
```
-
+
Existing settings being converted to a cascading setting will require individual
- migrations to add columns and change existing columns. Use the specifications
- below to create migrations as required:
-
+ migrations to add columns and change existing columns. Use the specifications
+ below to create migrations as required:
+
1. Columns in `namespace_settings` table:
- `delayed_project_removal`: No default value. Null values allowed. Use any column type.
- `lock_delayed_project_removal`: Boolean column. Default value is false. Null values not allowed.
1. Columns in `application_settings` table:
- - `delayed_project_removal`: Type matching for the column created in `namespace_settings`.
- Set default value as desired. Null values not allowed.
- - `lock_delayed_project_removal`: Boolean column. Default value is false. Null values not allowed.
-
+ - `delayed_project_removal`: Type matching for the column created in `namespace_settings`.
+ Set default value as desired. Null values not allowed.
+ - `lock_delayed_project_removal`: Boolean column. Default value is false. Null values not allowed.
+
## Convenience methods
By defining an attribute using the `cascading_attr` method, a number of convenience
-methods are automatically defined.
+methods are automatically defined.
**Definition:**
@@ -88,7 +88,7 @@ The attribute reader method (`delayed_project_removal`) returns the correct
cascaded value using the following criteria:
1. Returns the dirty value, if the attribute has changed. This allows standard
- Rails validators to be used on the attribute, though `nil` values *must* be allowed.
+ Rails validators to be used on the attribute, though `nil` values *must* be allowed.
1. Return locked ancestor value.
1. Return locked instance-level application settings value.
1. Return this namespace's attribute, if not nil.
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 705a69765f7..b454d1a88e0 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -241,8 +241,8 @@ cron worker runs. Default value is 5 minutes.
- `pause_indexing!` - Pause indexing while the migration runs. This setting will record the indexing setting before
the migration runs and set it back to that value when the migration is completed.
-- `space_requirements!` - Verify that enough free space is available in the cluster when the migration runs. This setting
- will halt the migration if the storage required is not available when the migration runs. The migration must provide
+- `space_requirements!` - Verify that enough free space is available in the cluster when the migration runs. This setting
+ will halt the migration if the storage required is not available when the migration runs. The migration must provide
the space required in bytes by defining a `space_required_bytes` method.
```ruby
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 574faa0898f..1cce699218c 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -102,9 +102,9 @@ across the codebase.
#### Providing Rails form fields to Vue applications
When composing a form with Rails, the `name`, `id`, and `value` attributes of form inputs are generated
-to match the backend. It can be helpful to have access to these generated attributes when converting
+to match the backend. It can be helpful to have access to these generated attributes when converting
a Rails form to Vue, or when [integrating components (datepicker, project selector, etc)](https://gitlab.com/gitlab-org/gitlab/-/blob/8956ad767d522f37a96e03840595c767de030968/app/assets/javascripts/access_tokens/index.js#L15) into it.
-The [`parseRailsFormFields`](https://gitlab.com/gitlab-org/gitlab/-/blob/fe88797f682c7ff0b13f2c2223a3ff45ada751c1/app/assets/javascripts/lib/utils/forms.js#L107) utility can be used to parse the generated form input attributes so they can be passed to the Vue application.
+The [`parseRailsFormFields`](https://gitlab.com/gitlab-org/gitlab/-/blob/fe88797f682c7ff0b13f2c2223a3ff45ada751c1/app/assets/javascripts/lib/utils/forms.js#L107) utility can be used to parse the generated form input attributes so they can be passed to the Vue application.
This allows us to easily integrate Vue components without changing how the form submits.
```haml
@@ -116,7 +116,7 @@ This allows us to easily integrate Vue components without changing how the form
```
> The `js_name` data attribute is used as the key in the resulting JavaScript object.
-For example `= form.text_field :email, data: { js_name: 'fooBarBaz' }` would be translated
+For example `= form.text_field :email, data: { js_name: 'fooBarBaz' }` would be translated
to `{ fooBarBaz: { name: 'user[email]', id: 'user_email', value: '' } }`
```javascript
diff --git a/doc/development/graphql_guide/authorization.md b/doc/development/graphql_guide/authorization.md
index ee5713f6fda..8f17a8b6c93 100644
--- a/doc/development/graphql_guide/authorization.md
+++ b/doc/development/graphql_guide/authorization.md
@@ -148,7 +148,7 @@ end
```
In this example, we use field authorization (such as
-`Ability.allowed?(current_user, :read_transactions, bank_account)`) to avoid
+`Ability.allowed?(current_user, :read_transactions, bank_account)`) to avoid
a more expensive query:
```ruby
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 9b636260c13..c6378fa9643 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -436,7 +436,7 @@ This mapping is currently generated using a combination of test coverage tracing
In the `detect-tests` job, we use this mapping to identify the minimal tests needed for the current Merge Request.
In this experiment, each `rspec` job is accompanied with a `minimal` version.
-For example, `rspec unit` job has a corresponding `rspec unit minimal` job.
+For example, `rspec unit` job has a corresponding `rspec unit minimal` job.
During the experiment, each Merge Request pipeline will contain both versions of the job, running in parallel.
To illustrate this:
@@ -462,7 +462,7 @@ graph LR
```
The result of both set of jobs in the pipeline is then compared to identify any false positive.
-A list of such pipeline can be found in [Sisense](https://app.periscopedata.com/app/gitlab/496118/Engineering-Productivity-Sandbox?widget=10492739&udv=833427).
+A list of such pipeline can be found in [Sisense](https://app.periscopedata.com/app/gitlab/496118/Engineering-Productivity-Sandbox?widget=10492739&udv=833427).
A false positive is defined as a pipeline where the `minimal` jobs passed, but the non-`minimal` jobs failed.
This indicates that the changeset resulted in a test failure, which was not detected by the `minimal` jobs.
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 7289e66a045..629689f8e83 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -1151,8 +1151,8 @@ Both functions run `callback` on the next tick after the requests finish (using
### `shallowMountExtended` and `mountExtended`
-The `shallowMountExtended` and `mountExtended` utilities provide you with the ability to perform
-any of the available [DOM Testing Library queries](https://testing-library.com/docs/queries/about)
+The `shallowMountExtended` and `mountExtended` utilities provide you with the ability to perform
+any of the available [DOM Testing Library queries](https://testing-library.com/docs/queries/about)
by prefixing them with `find` or `findAll`.
```javascript
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 70ec349399a..76bf9639250 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -841,6 +841,6 @@ If you see this page when trying to set a password via the web interface, make s
### Some job logs are not uploaded to object storage
-When the GitLab deployment is scaled up to more than one node, some job logs may not be uploaded to [object storage](../../administration/object_storage.md) properly. [Incremental logging is required](../../administration/object_storage.md#incremental-logging-is-required-for-ci-to-use-object-storage) for CI to use object storage.
+When the GitLab deployment is scaled up to more than one node, some job logs may not be uploaded to [object storage](../../administration/object_storage.md) properly. [Incremental logging is required](../../administration/object_storage.md#incremental-logging-is-required-for-ci-to-use-object-storage) for CI to use object storage.
-Enable [incremental logging](../../administration/job_logs.md#enabling-incremental-logging) if it has not already been enabled.
+Enable [incremental logging](../../administration/job_logs.md#enabling-incremental-logging) if it has not already been enabled.
diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md
index c675d995444..acfab79c0c3 100644
--- a/doc/operations/incident_management/integrations.md
+++ b/doc/operations/incident_management/integrations.md
@@ -168,7 +168,7 @@ alert to confirm your integration works properly.
1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
1. Navigate to **Settings > Operations** in your project.
1. Click **Alert integrations** to expand the section.
-1. Click the **{settings}** settings icon on the right side of the integration in [the list](#integrations-list).
+1. Click the **{settings}** settings icon on the right side of the integration in [the list](#integrations-list).
1. Select the **Send test alert** tab to open it.
1. Enter a test payload in the payload field (valid JSON is required).
1. Click **Send**.
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
index eba471882f1..eb1c0a597f3 100644
--- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -379,7 +379,7 @@ it also provides a clear timeline and development structure.
![Use revert to keep branch flowing](img/revert.png)
-If you want to revert changes introduced in certain `commit-id`, you can
+If you want to revert changes introduced in certain `commit-id`, you can
revert that `commit-id` (swap additions and deletions) in newly created commit:
You can do this with
diff --git a/doc/update/restore_after_failure.md b/doc/update/restore_after_failure.md
index 0847fc82f19..e9c2c69ecb9 100644
--- a/doc/update/restore_after_failure.md
+++ b/doc/update/restore_after_failure.md
@@ -12,7 +12,7 @@ However, it's important to know how to recover when problems do arise.
## Roll back to an earlier version and restore a backup
In some cases after a failed upgrade, the fastest solution is to roll back to
-the previous version you were using. We recommend this path because the failed
+the previous version you were using. We recommend this path because the failed
upgrade will likely have made database changes that can not be readily reverted.
First, roll back the code or package. For source installations this involves
diff --git a/doc/user/application_security/api_fuzzing/index.md b/doc/user/application_security/api_fuzzing/index.md
index dfb7e12a8be..f8e861500a4 100644
--- a/doc/user/application_security/api_fuzzing/index.md
+++ b/doc/user/application_security/api_fuzzing/index.md
@@ -1157,7 +1157,7 @@ For OpenAPI specifications that are generated automatically validation errors ar
- `OpenAPI 2.0 schema validation error ...`
- `OpenAPI 3.0.x schema validation error ...`
-**Solution**
+**Solution**
**For generated OpenAPI specifications**
@@ -1183,7 +1183,7 @@ The API Fuzzing engine outputs an error message when it cannot establish a conne
- In [GitLab 13.11 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/323939), `Failed to start scanner session (version header not found).`
- In GitLab 13.10 and earlier, `API Security version header not found. Are you sure that you are connecting to the API Security server?`.
-**Solution**
+**Solution**
- Remove the `FUZZAPI_API` variable from the `.gitlab-ci.yml` file. The value will be inherited from the API Fuzzing CI/CD template. We recommend this method instead of manually setting a value.
- If removing the variable is not possible, check to see if this value has changed in the latest version of the [API Fuzzing CI/CD template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml). If so, update the value in the `.gitlab-ci.yml` file.
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 65ddece1bde..73da4d4c832 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -948,11 +948,11 @@ A site profile contains the following:
- **Excluded URLs**: A comma-separated list of URLs to exclude from the scan.
- **Request headers**: A comma-separated list of HTTP request headers, including names and values. These headers are added to every request made by DAST.
- **Authentication**:
- - **Authenticated URL**: The URL of the page containing the sign-in HTML form on the target website. The username and password are submitted with the login form to create an authenticated scan.
+ - **Authenticated URL**: The URL of the page containing the sign-in HTML form on the target website. The username and password are submitted with the login form to create an authenticated scan.
- **Username**: The username used to authenticate to the website.
- **Password**: The password used to authenticate to the website.
- - **Username form field**: The name of username field at the sign-in HTML form.
- - **Password form field**: The name of password field at the sign-in HTML form.
+ - **Username form field**: The name of username field at the sign-in HTML form.
+ - **Password form field**: The name of password field at the sign-in HTML form.
#### Site profile validation
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
index c085dafac12..0e69f3b68eb 100644
--- a/doc/user/application_security/sast/analyzers.md
+++ b/doc/user/application_security/sast/analyzers.md
@@ -45,7 +45,7 @@ GitLab, but users can also integrate their own **custom images**.
## SAST analyzer features
-For an analyzer to be considered Generally Available, it is expected to minimally
+For an analyzer to be considered Generally Available, it is expected to minimally
support the following features:
- [Customizable configuration](index.md#available-variables)
diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md
index 823a0561beb..b163f19cdc5 100644
--- a/doc/user/compliance/license_compliance/index.md
+++ b/doc/user/compliance/license_compliance/index.md
@@ -766,7 +766,7 @@ Defining a non-latest Python version in ASDF_PYTHON_VERSION [doesn't have it aut
1. Define the required version by setting the `ASDF_PYTHON_VERSION` CI/CD variable.
1. Pass a custom script to the `SETUP_CMD` CI/CD variable to install the required version and dependencies.
-For example:
+For example:
```yaml
include:
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index bd31fab6921..b5b461b16c1 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -69,7 +69,7 @@ must meet one of the following conditions prior to the import:
GitLab content imports that use GitHub accounts require that the GitHub public-facing
email address is populated so that all comments and contributions are properly mapped
to the same user in GitLab. GitHub Enterprise (on premise) does not require this field
- to be populated to use the product, so you may need to add it on existing GitHub Enterprise
+ to be populated to use the product, so you may need to add it on existing GitHub Enterprise
accounts for imported content to be properly mapped to the user in the new system.
Refer to GitHub documentation for instructions on how to add that address.
diff --git a/doc/user/project/integrations/ewm.md b/doc/user/project/integrations/ewm.md
index d63599d02bc..5b0059673ad 100644
--- a/doc/user/project/integrations/ewm.md
+++ b/doc/user/project/integrations/ewm.md
@@ -6,31 +6,49 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# IBM Engineering Workflow Management (EWM) Integration **(FREE)**
-This service allows you to navigate from GitLab to EWM work items mentioned in merge request descriptions and commit messages. Each work item reference is automatically converted to a link back to the work item.
+This service allows you to go from GitLab to EWM work items mentioned in merge request
+descriptions and commit messages.
+Each work item reference is automatically converted to a link to the work item.
-NOTE:
-This IBM product was [formerly named Rational Team Concert](https://jazz.net/blog/index.php/2019/04/23/renaming-the-ibm-continuous-engineering-portfolio/)(RTC). This integration is also compatible with all versions of RTC and EWM.
+This IBM product was [formerly named Rational Team Concert](https://jazz.net/blog/index.php/2019/04/23/renaming-the-ibm-continuous-engineering-portfolio/)(RTC). This integration is compatible with all versions of RTC and EWM.
-1. From a GitLab project, navigate to **Settings > Integrations**, and then click **EWM**.
-1. Enter the information listed below.
+To enable the EWM integration, in a project:
- | Field | Description |
- | ----- | ----------- |
- | `project_url` | URL of the EWM project area to link to the GitLab project. To obtain your project area URL, navigate to the path `/ccm/web/projects` and copy the listed project's URL. For example, `https://example.com/ccm/web/Example%20Project` |
- | `issues_url` | URL to the work item editor in the EWM project area. The format is `<your-server-url>/resource/itemName/com.ibm.team.workitem.WorkItem/:id`. For example, `https://example.com/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/:id` |
- | `new_issue_url` | URL to create a new work item in the EWM project area. Append the following fragment to your project area URL: `#action=com.ibm.team.workitem.newWorkItem`. For example, `https://example.com/ccm/web/projects/JKE%20Banking#action=com.ibm.team.workitem.newWorkItem` |
+1. Go to the [Integrations page](overview.md#accessing-integrations).
+1. Select **EWM**.
+1. Select the checkbox under **Enable integration**.
+1. Fill in the required fields:
+
+ - **Project URL**: The URL to the EWM project area.
+
+ To obtain your project area URL, navigate to the
+ path `/ccm/web/projects` and copy the listed project's URL. For example, `https://example.com/ccm/web/Example%20Project`.
+ - **Issue URL**: The URL to the work item editor in the EWM project area.
+
+ The format is `<your-server-url>/resource/itemName/com.ibm.team.workitem.WorkItem/:id`.
+ GitLab replaces `:id` with the issue number
+ (for example, `https://example.com/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/:id`,
+ which becomes `https://example.com/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/123`).
+ - **New issue URL**: URL to create a new work item in the EWM project area.
+
+ Append the following fragment to your project area URL: `#action=com.ibm.team.workitem.newWorkItem`.
+ For example, `https://example.com/ccm/web/projects/JKE%20Banking#action=com.ibm.team.workitem.newWorkItem`.
+
+1. Select **Save changes** or optionally select **Test settings**.
## Reference EWM work items in commit messages
-You can use any of the keywords supported by the EWM Git Integration Toolkit to refer to work items. Work items can be referenced using the format: `<keyword> <id>`.
+To refer to work items, you can use any keywords supported by the EWM Git Integration Toolkit.
+Use the format: `<keyword> <id>`.
You can use the following keywords:
- `bug`
-- `task`
- `defect`
- `rtcwi`
-- `workitem`
+- `task`
- `work item`
+- `workitem`
-For more details, see the EWM documentation page [Creating links from commit comments](https://www.ibm.com/support/knowledgecenter/SSYMRC_7.0.0/com.ibm.team.connector.cq.doc/topics/t_creating_links_through_comments.html), which recommends against using the additionally-supported keyword `#` because of incompatibility with GitLab.
+Avoid using the keyword `#`. Learn more in the EWM documentation page
+[Creating links from commit comments](https://www.ibm.com/docs/en/elm/7.0.0?topic=commits-creating-links-from-commit-comments).
diff --git a/doc/user/project/time_tracking.md b/doc/user/project/time_tracking.md
index 402422cb3f3..0bde6045329 100644
--- a/doc/user/project/time_tracking.md
+++ b/doc/user/project/time_tracking.md
@@ -56,7 +56,7 @@ To remove an estimation entirely, use `/remove_estimate`.
### Time spent
-To enter time spent, write `/spend`, followed by the time. For example, if you need
+To enter time spent, write `/spend`, followed by the time. For example, if you need
to log 1 month, 2 weeks, 3 days, 4 hours and 5 minutes, you would write `/spend 1mo 2w 3d 4h 5m`.
Time units that we support are listed at the bottom of this help page.
@@ -68,7 +68,7 @@ days from the total time spent. You can't go below 0 minutes of time spent,
so GitLab automatically resets the time spent if you remove a larger amount
of time compared to the time that was entered already.
-You can log time in the past by providing a date after the time.
+You can log time in the past by providing a date after the time.
For example, if you want to log 1 hour of time spent on the 31 January 2021,
you would write `/spend 1h 2021-01-31`. If you supply a date in the future, the
command fails and no time is logged.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f9cfaef76a6..c67c482ae42 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17959,7 +17959,10 @@ msgstr ""
msgid "IssueTracker|Custom issue tracker"
msgstr ""
-msgid "IssueTracker|EWM work items tracker"
+msgid "IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker."
+msgstr ""
+
+msgid "IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker. %{docs_link}"
msgstr ""
msgid "IssueTracker|Use Redmine as the issue tracker."
diff --git a/spec/frontend/issues_list/components/issues_list_app_spec.js b/spec/frontend/issues_list/components/issues_list_app_spec.js
index 476804bda12..65bdcc8d219 100644
--- a/spec/frontend/issues_list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues_list/components/issues_list_app_spec.js
@@ -3,12 +3,12 @@ import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
+import { filteredTokens, locationSearch } from 'jest/issues_list/mock_data';
import createFlash from '~/flash';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
-
import {
CREATED_DESC,
PAGE_SIZE,
@@ -29,17 +29,19 @@ describe('IssuesListApp component', () => {
let wrapper;
const defaultProvide = {
+ autocompleteUsersPath: 'autocomplete/users/path',
calendarPath: 'calendar/path',
canBulkUpdate: false,
emptyStateSvgPath: 'empty-state.svg',
endpoint: 'api/endpoint',
exportCsvPath: 'export/csv/path',
- fullPath: 'path/to/project',
hasIssues: true,
isSignedIn: false,
issuesPath: 'path/to/issues',
jiraIntegrationPath: 'jira/integration/path',
newIssuePath: 'new/issue/path',
+ projectLabelsPath: 'project/labels/path',
+ projectPath: 'path/to/project',
rssPath: 'rss/path',
showImportButton: true,
showNewIssueLink: true,
@@ -99,7 +101,7 @@ describe('IssuesListApp component', () => {
it('renders', () => {
expect(findIssuableList().props()).toMatchObject({
- namespace: defaultProvide.fullPath,
+ namespace: defaultProvide.projectPath,
recentSearchesStorageKey: 'issues',
searchInputPlaceholder: 'Search or filter results…',
sortOptions,
@@ -213,6 +215,19 @@ describe('IssuesListApp component', () => {
});
});
+ describe('search', () => {
+ it('is set from the url params', () => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: { search: locationSearch },
+ });
+
+ wrapper = mountComponent();
+
+ expect(findIssuableList().props('urlParams')).toMatchObject({ search: 'find issues' });
+ });
+ });
+
describe('sort', () => {
it.each(Object.keys(sortParams))('is set as %s from the url params', (sortKey) => {
Object.defineProperty(window, 'location', {
@@ -243,6 +258,19 @@ describe('IssuesListApp component', () => {
expect(findIssuableList().props('currentTab')).toBe(initialState);
});
});
+
+ describe('filter tokens', () => {
+ it('is set from the url params', () => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: { search: locationSearch },
+ });
+
+ wrapper = mountComponent();
+
+ expect(findIssuableList().props('initialFilterValue')).toEqual(filteredTokens);
+ });
+ });
});
describe('bulk edit', () => {
@@ -265,15 +293,13 @@ describe('IssuesListApp component', () => {
describe('empty states', () => {
describe('when there are issues', () => {
describe('when search returns no results', () => {
- beforeEach(async () => {
+ beforeEach(() => {
Object.defineProperty(window, 'location', {
writable: true,
- value: { href: setUrlParams({ search: 'no results' }, TEST_HOST) },
+ value: { search: '?search=no+results' },
});
wrapper = mountComponent({ provide: { hasIssues: true } });
-
- await waitForPromises();
});
it('shows empty state', () => {
@@ -418,7 +444,7 @@ describe('IssuesListApp component', () => {
});
it('fetches issues with expected params', () => {
- expect(axiosMock.history.get[1].params).toEqual({
+ expect(axiosMock.history.get[1].params).toMatchObject({
page,
per_page: PAGE_SIZE,
state,
@@ -525,21 +551,32 @@ describe('IssuesListApp component', () => {
});
describe('when "filter" event is emitted by IssuableList', () => {
- beforeEach(async () => {
+ beforeEach(() => {
wrapper = mountComponent();
- const payload = [
- { type: 'filtered-search-term', value: { data: 'no' } },
- { type: 'filtered-search-term', value: { data: 'issues' } },
- ];
-
- findIssuableList().vm.$emit('filter', payload);
-
- await waitForPromises();
+ findIssuableList().vm.$emit('filter', filteredTokens);
});
it('makes an API call to search for issues with the search term', () => {
- expect(axiosMock.history.get[1].params).toMatchObject({ search: 'no issues' });
+ expect(axiosMock.history.get[1].params).toMatchObject({
+ author_username: 'homer',
+ 'not[author_username]': 'marge',
+ assignee_username: 'bart',
+ 'not[assignee_username]': 'lisa',
+ labels: 'cartoon,tv',
+ 'not[labels]': 'live action,drama',
+ });
+ });
+
+ it('updates IssuableList with url params', () => {
+ expect(findIssuableList().props('urlParams')).toMatchObject({
+ author_username: ['homer'],
+ 'not[author_username]': ['marge'],
+ 'assignee_username[]': ['bart'],
+ 'not[assignee_username][]': ['lisa'],
+ 'label_name[]': ['cartoon', 'tv'],
+ 'not[label_name][]': ['live action', 'drama'],
+ });
});
});
});
diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js
new file mode 100644
index 00000000000..e036c26689c
--- /dev/null
+++ b/spec/frontend/issues_list/mock_data.js
@@ -0,0 +1,26 @@
+import { OPERATOR_IS, OPERATOR_IS_NOT } from '~/issues_list/constants';
+
+export const locationSearch = [
+ '?search=find+issues',
+ 'author_username=homer',
+ 'not[author_username]=marge',
+ 'assignee_username[]=bart',
+ 'not[assignee_username][]=lisa',
+ 'label_name[]=cartoon',
+ 'label_name[]=tv',
+ 'not[label_name][]=live action',
+ 'not[label_name][]=drama',
+].join('&');
+
+export const filteredTokens = [
+ { type: 'author_username', value: { data: 'homer', operator: OPERATOR_IS } },
+ { type: 'author_username', value: { data: 'marge', operator: OPERATOR_IS_NOT } },
+ { type: 'assignee_username', value: { data: 'bart', operator: OPERATOR_IS } },
+ { type: 'assignee_username', value: { data: 'lisa', operator: OPERATOR_IS_NOT } },
+ { type: 'labels', value: { data: 'cartoon', operator: OPERATOR_IS } },
+ { type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } },
+ { type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } },
+ { type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } },
+ { type: 'filtered-search-term', value: { data: 'find' } },
+ { type: 'filtered-search-term', value: { data: 'issues' } },
+];
diff --git a/spec/frontend/issues_list/utils_spec.js b/spec/frontend/issues_list/utils_spec.js
new file mode 100644
index 00000000000..31e4d4e5f45
--- /dev/null
+++ b/spec/frontend/issues_list/utils_spec.js
@@ -0,0 +1,54 @@
+import { filteredTokens, locationSearch } from 'jest/issues_list/mock_data';
+import { sortParams } from '~/issues_list/constants';
+import {
+ convertToApiParams,
+ convertToSearchQuery,
+ convertToUrlParams,
+ getFilterTokens,
+ getSortKey,
+} from '~/issues_list/utils';
+
+describe('getSortKey', () => {
+ it.each(Object.keys(sortParams))('returns %s given the correct inputs', (sortKey) => {
+ const { order_by, sort } = sortParams[sortKey];
+ expect(getSortKey(order_by, sort)).toBe(sortKey);
+ });
+});
+
+describe('getFilterTokens', () => {
+ it('returns filtered tokens given "window.location.search"', () => {
+ expect(getFilterTokens(locationSearch)).toEqual(filteredTokens);
+ });
+});
+
+describe('convertToApiParams', () => {
+ it('returns api params given filtered tokens', () => {
+ expect(convertToApiParams(filteredTokens)).toEqual({
+ author_username: 'homer',
+ 'not[author_username]': 'marge',
+ assignee_username: 'bart',
+ 'not[assignee_username]': 'lisa',
+ labels: 'cartoon,tv',
+ 'not[labels]': 'live action,drama',
+ });
+ });
+});
+
+describe('convertToUrlParams', () => {
+ it('returns url params given filtered tokens', () => {
+ expect(convertToUrlParams(filteredTokens)).toEqual({
+ author_username: ['homer'],
+ 'not[author_username]': ['marge'],
+ 'assignee_username[]': ['bart'],
+ 'not[assignee_username][]': ['lisa'],
+ 'label_name[]': ['cartoon', 'tv'],
+ 'not[label_name][]': ['live action', 'drama'],
+ });
+ });
+});
+
+describe('convertToSearchQuery', () => {
+ it('returns search string given filtered tokens', () => {
+ expect(convertToSearchQuery(filteredTokens)).toBe('find issues');
+ });
+});
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 21a01f349b5..c4eb69d8307 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -293,6 +293,7 @@ RSpec.describe IssuesHelper do
allow(helper).to receive(:url_for).and_return('#')
expected = {
+ autocomplete_users_path: autocomplete_users_path(active: true, current_user: true, project_id: project.id, format: :json),
calendar_path: '#',
can_bulk_update: 'true',
can_edit: 'true',
@@ -301,7 +302,6 @@ RSpec.describe IssuesHelper do
empty_state_svg_path: '#',
endpoint: expose_path(api_v4_projects_issues_path(id: project.id)),
export_csv_path: export_csv_project_issues_path(project),
- full_path: project.full_path,
has_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: '#',
is_signed_in: current_user.present?.to_s,
@@ -310,6 +310,8 @@ RSpec.describe IssuesHelper do
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project),
+ project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
+ project_path: project.full_path,
rss_path: '#',
show_new_issue_link: 'true',
sign_in_path: new_user_session_path