Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6143ef2fb6
commit
cd4d8b60a0
|
@ -27,7 +27,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
urlParams() {
|
||||
const { authorUsername, labelName, search } = this.filterParams;
|
||||
const { authorUsername, labelName, assigneeUsername, search } = this.filterParams;
|
||||
let notParams = {};
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) {
|
||||
|
@ -35,6 +35,7 @@ export default {
|
|||
{
|
||||
'not[label_name][]': this.filterParams.not.labelName,
|
||||
'not[author_username]': this.filterParams.not.authorUsername,
|
||||
'not[assignee_username]': this.filterParams.not.assigneeUsername,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
|
@ -44,6 +45,7 @@ export default {
|
|||
...notParams,
|
||||
author_username: authorUsername,
|
||||
'label_name[]': labelName,
|
||||
assignee_username: assigneeUsername,
|
||||
search,
|
||||
};
|
||||
},
|
||||
|
@ -62,7 +64,7 @@ export default {
|
|||
this.performSearch();
|
||||
},
|
||||
getFilteredSearchValue() {
|
||||
const { authorUsername, labelName, search } = this.filterParams;
|
||||
const { authorUsername, labelName, assigneeUsername, search } = this.filterParams;
|
||||
const filteredSearchValue = [];
|
||||
|
||||
if (authorUsername) {
|
||||
|
@ -72,6 +74,13 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (assigneeUsername) {
|
||||
filteredSearchValue.push({
|
||||
type: 'assignee_username',
|
||||
value: { data: assigneeUsername, operator: '=' },
|
||||
});
|
||||
}
|
||||
|
||||
if (labelName?.length) {
|
||||
filteredSearchValue.push(
|
||||
...labelName.map((label) => ({
|
||||
|
@ -88,6 +97,13 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.filterParams['not[assigneeUsername]']) {
|
||||
filteredSearchValue.push({
|
||||
type: 'assignee_username',
|
||||
value: { data: this.filterParams['not[assigneeUsername]'], operator: '!=' },
|
||||
});
|
||||
}
|
||||
|
||||
if (this.filterParams['not[labelName]']) {
|
||||
filteredSearchValue.push(
|
||||
...this.filterParams['not[labelName]'].map((label) => ({
|
||||
|
@ -121,6 +137,9 @@ export default {
|
|||
case 'author_username':
|
||||
filterParams.authorUsername = filter.value.data;
|
||||
break;
|
||||
case 'assignee_username':
|
||||
filterParams.assigneeUsername = filter.value.data;
|
||||
break;
|
||||
case 'label_name':
|
||||
labels.push(filter.value.data);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<script>
|
||||
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
|
||||
import issueBoardFilters from '~/boards/issue_board_filters';
|
||||
import { TYPE_USER } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
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';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
search: __('Search'),
|
||||
label: __('Label'),
|
||||
author: __('Author'),
|
||||
assignee: __('Assignee'),
|
||||
is: __('is'),
|
||||
isNot: __('is not'),
|
||||
},
|
||||
components: { BoardFilteredSearch },
|
||||
props: {
|
||||
fullPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
boardType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tokens() {
|
||||
const { label, is, isNot, author, assignee } = this.$options.i18n;
|
||||
const { fetchAuthors, fetchLabels } = issueBoardFilters(
|
||||
this.$apollo,
|
||||
this.fullPath,
|
||||
this.boardType,
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
icon: 'labels',
|
||||
title: label,
|
||||
type: 'label_name',
|
||||
operators: [
|
||||
{ value: '=', description: is },
|
||||
{ value: '!=', description: isNot },
|
||||
],
|
||||
token: LabelToken,
|
||||
unique: false,
|
||||
symbol: '~',
|
||||
fetchLabels,
|
||||
},
|
||||
{
|
||||
icon: 'pencil',
|
||||
title: author,
|
||||
type: 'author_username',
|
||||
operators: [
|
||||
{ value: '=', description: is },
|
||||
{ value: '!=', description: isNot },
|
||||
],
|
||||
symbol: '@',
|
||||
token: AuthorToken,
|
||||
unique: true,
|
||||
fetchAuthors,
|
||||
preloadedAuthors: this.preloadedAuthors(),
|
||||
},
|
||||
{
|
||||
icon: 'user',
|
||||
title: assignee,
|
||||
type: 'assignee_username',
|
||||
operators: [
|
||||
{ value: '=', description: is },
|
||||
{ value: '!=', description: isNot },
|
||||
],
|
||||
token: AuthorToken,
|
||||
unique: true,
|
||||
fetchAuthors,
|
||||
preloadedAuthors: this.preloadedAuthors(),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
preloadedAuthors() {
|
||||
return gon?.current_user_id
|
||||
? [
|
||||
{
|
||||
id: convertToGraphQLId(TYPE_USER, gon.current_user_id),
|
||||
name: gon.current_user_fullname,
|
||||
username: gon.current_username,
|
||||
avatarUrl: gon.current_user_avatar_url,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<board-filtered-search data-testid="issue-board-filtered-search" :tokens="tokens" />
|
||||
</template>
|
|
@ -25,6 +25,7 @@ import '~/boards/filters/due_date_filters';
|
|||
import { issuableTypes } from '~/boards/constants';
|
||||
import eventHub from '~/boards/eventhub';
|
||||
import FilteredSearchBoards from '~/boards/filtered_search_boards';
|
||||
import initBoardsFilteredSearch from '~/boards/mount_filtered_search_issue_boards';
|
||||
import store from '~/boards/stores';
|
||||
import boardsStore from '~/boards/stores/boards_store';
|
||||
import toggleFocusMode from '~/boards/toggle_focus';
|
||||
|
@ -78,6 +79,10 @@ export default () => {
|
|||
issueBoardsApp.$destroy(true);
|
||||
}
|
||||
|
||||
if (gon?.features?.issueBoardsFilteredSearch) {
|
||||
initBoardsFilteredSearch(apolloProvider);
|
||||
}
|
||||
|
||||
if (!gon?.features?.graphqlBoardLists) {
|
||||
boardsStore.create();
|
||||
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
|
||||
|
@ -184,9 +189,14 @@ export default () => {
|
|||
eventHub.$off('initialBoardLoad', this.initialBoardLoad);
|
||||
},
|
||||
mounted() {
|
||||
this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
|
||||
|
||||
this.filterManager.setup();
|
||||
if (!gon?.features?.issueBoardsFilteredSearch) {
|
||||
this.filterManager = new FilteredSearchBoards(
|
||||
boardsStore.filter,
|
||||
true,
|
||||
boardsStore.cantEdit,
|
||||
);
|
||||
this.filterManager.setup();
|
||||
}
|
||||
|
||||
this.performSearch();
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import groupBoardMembers from '~/boards/graphql/group_board_members.query.graphql';
|
||||
import projectBoardMembers from '~/boards/graphql/project_board_members.query.graphql';
|
||||
import { BoardType } from './constants';
|
||||
import boardLabels from './graphql/board_labels.query.graphql';
|
||||
|
||||
export default function issueBoardFilters(apollo, fullPath, boardType) {
|
||||
const isGroupBoard = boardType === BoardType.group;
|
||||
const isProjectBoard = boardType === BoardType.project;
|
||||
const transformLabels = ({ data }) => {
|
||||
return isGroupBoard ? data.group?.labels.nodes || [] : data.project?.labels.nodes || [];
|
||||
};
|
||||
|
||||
const boardAssigneesQuery = () => {
|
||||
return isGroupBoard ? groupBoardMembers : projectBoardMembers;
|
||||
};
|
||||
|
||||
const fetchAuthors = (authorsSearchTerm) => {
|
||||
return apollo
|
||||
.query({
|
||||
query: boardAssigneesQuery(),
|
||||
variables: {
|
||||
fullPath,
|
||||
search: authorsSearchTerm,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data.workspace?.assignees.nodes.map(({ user }) => user));
|
||||
};
|
||||
|
||||
const fetchLabels = (labelSearchTerm) => {
|
||||
return apollo
|
||||
.query({
|
||||
query: boardLabels,
|
||||
variables: {
|
||||
fullPath,
|
||||
searchTerm: labelSearchTerm,
|
||||
isGroup: isGroupBoard,
|
||||
isProject: isProjectBoard,
|
||||
},
|
||||
})
|
||||
.then(transformLabels);
|
||||
};
|
||||
|
||||
return {
|
||||
fetchLabels,
|
||||
fetchAuthors,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import Vue from 'vue';
|
||||
import IssueBoardFilteredSearch from '~/boards/components/issue_board_filtered_search.vue';
|
||||
import store from '~/boards/stores';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { queryToObject } from '~/lib/utils/url_utility';
|
||||
|
||||
export default (apolloProvider) => {
|
||||
const el = document.getElementById('js-issue-board-filtered-search');
|
||||
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
|
||||
|
||||
const initialFilterParams = {
|
||||
...convertObjectPropsToCamelCase(rawFilterParams, {}),
|
||||
};
|
||||
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
provide: {
|
||||
initialFilterParams,
|
||||
},
|
||||
store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094
|
||||
apolloProvider,
|
||||
render: (createElement) =>
|
||||
createElement(IssueBoardFilteredSearch, {
|
||||
props: { fullPath: store.state?.fullPath || '', boardType: store.state?.boardType || '' },
|
||||
}),
|
||||
});
|
||||
};
|
|
@ -178,7 +178,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<div v-if="isGroupPendingRemoval">
|
||||
<gl-badge variant="warning">{{ __('pending removal') }}</gl-badge>
|
||||
<gl-badge variant="warning">{{ __('pending deletion') }}</gl-badge>
|
||||
</div>
|
||||
<div class="metadata d-flex flex-grow-1 flex-shrink-0 flex-wrap justify-content-md-between">
|
||||
<item-actions
|
||||
|
|
|
@ -73,7 +73,7 @@ export default {
|
|||
icon-name="star"
|
||||
/>
|
||||
<div v-if="isProjectPendingRemoval">
|
||||
<gl-badge variant="warning">{{ __('pending removal') }}</gl-badge>
|
||||
<gl-badge variant="warning">{{ __('pending deletion') }}</gl-badge>
|
||||
</div>
|
||||
<div v-if="isProject" class="last-updated">
|
||||
<time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" />
|
||||
|
|
|
@ -101,7 +101,7 @@ export default {
|
|||
v-if="isGroupPendingRemoval"
|
||||
variant="warning"
|
||||
class="gl-display-none gl-sm-display-flex gl-mt-3 gl-mr-1"
|
||||
>{{ __('pending removal') }}</gl-badge
|
||||
>{{ __('pending deletion') }}</gl-badge
|
||||
>
|
||||
<user-access-role-badge v-if="group.permission" class="gl-mt-3">
|
||||
{{ group.permission }}
|
||||
|
|
|
@ -103,6 +103,7 @@ export default {
|
|||
v-model="targetBranch"
|
||||
class="gl-font-monospace!"
|
||||
required
|
||||
data-qa-selector="target_branch_field"
|
||||
/>
|
||||
<gl-form-checkbox
|
||||
v-if="!isCurrentBranchTarget"
|
||||
|
|
|
@ -211,6 +211,7 @@ export default {
|
|||
:header-text="$options.i18n.dropdownHeader"
|
||||
:text="currentBranch"
|
||||
icon="branch"
|
||||
data-qa-selector="branch_selector_button"
|
||||
>
|
||||
<gl-search-box-by-type :debounce="$options.inputDebounce" @input="setSearchTerm" />
|
||||
<gl-dropdown-section-header>
|
||||
|
@ -228,6 +229,7 @@ export default {
|
|||
:key="branch"
|
||||
:is-checked="currentBranch === branch"
|
||||
:is-check-item="true"
|
||||
data-qa-selector="menu_branch_button"
|
||||
@click="selectBranch(branch)"
|
||||
>
|
||||
{{ branch }}
|
||||
|
|
|
@ -8,6 +8,7 @@ class Groups::BoardsController < Groups::ApplicationController
|
|||
before_action :assign_endpoint_vars
|
||||
before_action do
|
||||
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
|
||||
push_frontend_feature_flag(:issue_boards_filtered_search, group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml)
|
||||
|
|
|
@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:graphql_board_lists, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
|
||||
end
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
= check_box_tag checkbox_id, nil, false, class: "check-all-issues left"
|
||||
- if is_epic_board
|
||||
#js-board-filtered-search{ data: { full_path: @group&.full_path } }
|
||||
- elsif Feature.enabled?(:issue_boards_filtered_search, board&.resource_parent) && board
|
||||
#js-issue-board-filtered-search
|
||||
- else
|
||||
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
|
||||
.filtered-search-box
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: issue_boards_filtered_search
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61752
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331649
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::product planning
|
||||
default_enabled: false
|
|
@ -193,7 +193,7 @@ on adding these events into GitLab:
|
|||
Don't see the event you want in any of the epics linked above? You can use the **Audit Event
|
||||
Proposal** issue template to
|
||||
[create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Audit%20Event%20Proposal)
|
||||
to request it.
|
||||
to request it, or you can [add it yourself](../development/audit_event_guide/).
|
||||
|
||||
### Disabled events
|
||||
|
||||
|
|
|
@ -350,6 +350,11 @@ If parsing JUnit report XML results in an error, an indicator is shown next to t
|
|||
|
||||
![Test Reports With Errors](img/pipelines_junit_test_report_with_errors_v13_10.png)
|
||||
|
||||
NOTE:
|
||||
GitLab.com has a 500,000 [test case parsing limit](../user/gitlab_com/#gitlab-cicd). Self-managed administrators can manage this setting on their instance.
|
||||
|
||||
GitLab does not parse very [large nodes](https://nokogiri.org/tutorials/parsing_an_html_xml_document.html#parse-options) of JUnit reports. There is [an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/268035) open to make this optional.
|
||||
|
||||
## Viewing JUnit screenshots on GitLab
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202114) in GitLab 13.0 behind the `:junit_pipeline_screenshots_view` feature flag, disabled by default.
|
||||
|
|
|
@ -9,6 +9,11 @@ description: 'Writing styles, markup, formatting, and other standards for GitLab
|
|||
|
||||
To help ensure consistency in the documentation, follow this guidance.
|
||||
|
||||
For guidance not on this page, we defer to these style guides:
|
||||
|
||||
- [Microsoft Style Guide](https://docs.microsoft.com/en-us/style-guide/welcome/)
|
||||
- [Google Developer Documentation Style Guide](https://developers.google.com/style)
|
||||
|
||||
<!-- vale off -->
|
||||
<!-- markdownlint-disable -->
|
||||
|
||||
|
@ -22,7 +27,13 @@ Use **administration**, **administrator**, **administer**, or **Admin Area** ins
|
|||
|
||||
## allow, enable
|
||||
|
||||
Try to avoid, unless you are talking about security-related features. For example, instead of "This feature allows you to create a pipeline," use "Use this feature to create a pipeline." This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. [View details](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows).
|
||||
Try to avoid, unless you are talking about security-related features. For example:
|
||||
|
||||
- Avoid: This feature allows you to create a pipeline.
|
||||
- Use instead: Use this feature to create a pipeline.
|
||||
|
||||
This phrasing is more active and is from the user perspective, rather than the person who implemented the feature.
|
||||
[View details in the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows).
|
||||
|
||||
## and/or
|
||||
|
||||
|
@ -44,13 +55,14 @@ Do not use when talking about the product or its features. The documentation des
|
|||
|
||||
When writing about the Developer role:
|
||||
|
||||
- Use a capital "D."
|
||||
- Do not use the phrase, "if you are a developer" to mean someone who is assigned the Developer
|
||||
role. Instead, write it out. "If you are assigned the Developer role..."
|
||||
- To describe a situation where the Developer role is the minimum required, use the phrase "at least
|
||||
the Developer role..."
|
||||
- Use a capital **D**.
|
||||
- Do not use the phrase, **if you are a developer** to mean someone who is assigned the Developer
|
||||
role. Instead, write it out. For example, **if you are assigned the Developer role**.
|
||||
- To describe a situation where the Developer role is the minimum required:
|
||||
- Avoid: **the Developer role or higher**
|
||||
- Use instead: **at least the Developer role**
|
||||
|
||||
Do not use "Developer permissions." A user who is assigned the Developer role has a set of associated permissions.
|
||||
Do not use **Developer permissions**. A user who is assigned the Developer role has a set of associated permissions.
|
||||
|
||||
## disable
|
||||
|
||||
|
@ -59,7 +71,7 @@ Use **inactive** or **off** instead. ([Vale](../testing.md#vale) rule: [`Inclusi
|
|||
|
||||
## easily
|
||||
|
||||
Do not use. If the user doesn't find the process to be these things, we lose their trust.
|
||||
Do not use. If the user doesn't find the process to be easy, we lose their trust.
|
||||
|
||||
## e.g.
|
||||
|
||||
|
@ -99,17 +111,18 @@ Refers to the product license for GitLab instances managed by customers themselv
|
|||
|
||||
When writing about the Guest role:
|
||||
|
||||
- Use a capital "G."
|
||||
- Do not use the phrase, "if you are a guest" to mean someone who is assigned the Guest role.
|
||||
Instead, write it out. "If you are assigned the Guest role..."
|
||||
- To describe a situation where the Guest role is the minimum required, use the phrase "at
|
||||
least the Guest role..."
|
||||
- Use a capital **G**.
|
||||
- Do not use the phrase, **if you are a guest** to mean someone who is assigned the Guest
|
||||
role. Instead, write it out. For example, **if you are assigned the Guest role**.
|
||||
- To describe a situation where the Guest role is the minimum required:
|
||||
- Avoid: **the Guest role or higher**
|
||||
- Use instead: **at least the Guest role**
|
||||
|
||||
Do not use "Guest permissions." A user who is assigned the Guest role has a set of associated permissions.
|
||||
Do not use **Guest permissions**. A user who is assigned the Guest role has a set of associated permissions.
|
||||
|
||||
## handy
|
||||
|
||||
Do not use. If the user doesn't find the process to be these things, we lose their trust.
|
||||
Do not use. If the user doesn't find the feature or process to be handy, we lose their trust.
|
||||
|
||||
## high availability, HA
|
||||
|
||||
|
@ -127,13 +140,14 @@ Do not use Latin abbreviations. Use **that is** instead. ([Vale](../testing.md#v
|
|||
|
||||
When writing about the Maintainer role:
|
||||
|
||||
- Use a capital "M."
|
||||
- Do not use the phrase, "if you are a maintainer" to mean someone who is assigned the Maintainer
|
||||
role. Instead, write it out. "If you are assigned the Maintainer role..."
|
||||
- To describe a situation where the Maintainer role is the minimum required, use the phrase "at
|
||||
least the Maintainer role..."
|
||||
- Use a capital **M**.
|
||||
- Do not use the phrase, **if you are a maintainer** to mean someone who is assigned the Maintainer
|
||||
role. Instead, write it out. For example, **if you are assigned the Maintainer role**.
|
||||
- To describe a situation where the Maintainer role is the minimum required:
|
||||
- Avoid: **the Maintainer role or higher**
|
||||
- Use instead: **at least the Maintainer role**
|
||||
|
||||
Do not use "Maintainer permissions." A user who is assigned the Maintainer role has a set of associated permissions.
|
||||
Do not use **Maintainer permissions**. A user who is assigned the Maintainer role has a set of associated permissions.
|
||||
|
||||
## mankind
|
||||
|
||||
|
@ -163,11 +177,11 @@ Lowercase. If you use **MR** as the acronym, spell it out on first use.
|
|||
|
||||
When writing about the Owner role:
|
||||
|
||||
- Use a capital "O."
|
||||
- Do not use the phrase, "if you are an owner" to mean someone who is assigned the Owner role.
|
||||
Instead, write it out. "If you are assigned the Owner role..."
|
||||
- Use a capital **O**.
|
||||
- Do not use the phrase, **if you are an owner** to mean someone who is assigned the Owner
|
||||
role. Instead, write it out. For example, **if you are assigned the Owner role**.
|
||||
|
||||
Do not use "Owner permissions." A user who is assigned the Owner role has a set of associated permissions.
|
||||
Do not use **Owner permissions**. A user who is assigned the Owner role has a set of associated permissions.
|
||||
|
||||
## permissions
|
||||
|
||||
|
@ -183,10 +197,16 @@ Do not use. Doing so may negatively affect other users and contributors, which i
|
|||
|
||||
## Reporter
|
||||
|
||||
When writing about the Reporter role, use a capital "R." Do not use the phrase, "if you are a reporter"
|
||||
to mean someone who is assigned the Reporter role. Instead, write it out. "If you are assigned the Reporter role..."
|
||||
When writing about the Reporter role:
|
||||
|
||||
Do not use "Reporter permissions." A user who is assigned the Reporter role has a set of associated permissions.
|
||||
- Use a capital **R**.
|
||||
- Do not use the phrase, **if you are a reporter** to mean someone who is assigned the Reporter
|
||||
role. Instead, write it out. For example, **if you are assigned the Reporter role**.
|
||||
- To describe a situation where the Reporter role is the minimum required:
|
||||
- Avoid: **the Reporter role or higher**
|
||||
- Use instead: **at least the Reporter role**
|
||||
|
||||
Do not use **Reporter permissions**. A user who is assigned the Reporter role has a set of associated permissions.
|
||||
|
||||
## roles
|
||||
|
||||
|
@ -202,10 +222,10 @@ Do not use when talking about increasing GitLab performance for additional users
|
|||
|
||||
## setup, set up
|
||||
|
||||
Use **setup** as a noun, and **set up** as a verb. Examples:
|
||||
Use **setup** as a noun, and **set up** as a verb. For example:
|
||||
|
||||
- `Your remote office setup is amazing.`
|
||||
- `To set up your remote office correctly, first consider the ergonomics of your work area.`
|
||||
- Your remote office setup is amazing.
|
||||
- To set up your remote office correctly, consider the ergonomics of your work area.
|
||||
|
||||
## simply, simple
|
||||
|
||||
|
@ -213,7 +233,7 @@ Do not use. If the user doesn't find the process to be simple, we lose their tru
|
|||
|
||||
## slashes
|
||||
|
||||
Instead of **and/or**, use **or** or another sensible construction. This rule also applies to other slashes, like **follow/unfollow**. Some exceptions (like **CI/CD**) are allowed.
|
||||
Instead of **and/or**, use **or** or re-write the sentence. This rule also applies to other slashes, like **follow/unfollow**. Some exceptions (like **CI/CD**) are allowed.
|
||||
|
||||
## slave
|
||||
|
||||
|
@ -221,11 +241,14 @@ Do not use. Another option is **secondary**. ([Vale](../testing.md#vale) rule: [
|
|||
|
||||
## subgroup
|
||||
|
||||
Use instead of `sub-group`.
|
||||
Use instead of **sub-group**.
|
||||
|
||||
## that
|
||||
|
||||
Do not use. Example: `the file that you save` can be `the file you save`.
|
||||
Do not use. For example:
|
||||
|
||||
- Avoid: The file that you save...
|
||||
- Use instead: The file you save...
|
||||
|
||||
## they
|
||||
|
||||
|
@ -235,7 +258,7 @@ a gender-neutral pronoun.
|
|||
|
||||
## useful
|
||||
|
||||
Do not use. If the user doesn't find the process to be these things, we lose their trust.
|
||||
Do not use. If the user doesn't find the process to be useful, we lose their trust.
|
||||
|
||||
## utilize
|
||||
|
||||
|
@ -247,12 +270,12 @@ Do not use Latin abbreviations. Use **with**, **through**, or **by using** inste
|
|||
|
||||
## we
|
||||
|
||||
Try to avoid "we" and focus instead on how the user can accomplish something in GitLab.
|
||||
Try to avoid **we** and focus instead on how the user can accomplish something in GitLab.
|
||||
|
||||
Instead of: We created a feature for you to add widgets.
|
||||
Use: Use widgets when you have work you want to organize.
|
||||
- Avoid: We created a feature for you to add widgets.
|
||||
- Instead, use: Use widgets when you have work you want to organize.
|
||||
|
||||
One exception: You can use "we recommend" instead of "it is recommended" or "GitLab recommends."
|
||||
One exception: You can use **we recommend** instead of **it is recommended** or **GitLab recommends**.
|
||||
|
||||
## whitelist
|
||||
|
||||
|
|
|
@ -267,7 +267,8 @@ Learn how to [export a project](import_export.md#importing-the-project) in GitLa
|
|||
|
||||
### Advanced settings
|
||||
|
||||
Here you can run housekeeping, archive, rename, transfer, [remove a fork relationship](#removing-a-fork-relationship), or remove a project.
|
||||
Here you can run housekeeping, archive, rename, transfer,
|
||||
[remove a fork relationship](#removing-a-fork-relationship), or delete a project.
|
||||
|
||||
#### Archiving a project
|
||||
|
||||
|
@ -372,17 +373,16 @@ To delete a project:
|
|||
1. In the "Delete project" section, click the **Delete project** button.
|
||||
1. Confirm the action when asked to.
|
||||
|
||||
This action:
|
||||
This action deletes a project including all associated resources (issues, merge requests, and so on).
|
||||
|
||||
- Deletes a project including all associated resources (issues, merge requests etc).
|
||||
- From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium](https://about.gitlab.com/pricing/) or higher tiers,
|
||||
group Owners can [configure](../../group/index.md#enable-delayed-project-removal) projects within a group
|
||||
to be deleted after a delayed period.
|
||||
When enabled, actual deletion happens after number of days
|
||||
specified in [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
|
||||
In [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) and later, on Premium or higher tiers,
|
||||
group Owners can [configure](../../group/index.md#enable-delayed-project-removal) projects in a group
|
||||
to be deleted after a delayed period.
|
||||
When enabled, actual deletion happens after number of days
|
||||
specified in [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
|
||||
|
||||
WARNING:
|
||||
The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to
|
||||
The default behavior of [delayed project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to
|
||||
[Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
|
||||
|
||||
#### Restore a project **(PREMIUM)**
|
||||
|
|
|
@ -10671,7 +10671,7 @@ msgstr ""
|
|||
msgid "Deleting the project will delete its repository and all related resources including issues, merge requests, etc."
|
||||
msgstr ""
|
||||
|
||||
msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only."
|
||||
msgid "Deletion pending. This project will be deleted on %{date}. Repository and other project resources are read-only."
|
||||
msgstr ""
|
||||
|
||||
msgid "Denied"
|
||||
|
@ -32661,7 +32661,7 @@ msgstr ""
|
|||
msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
|
||||
msgstr ""
|
||||
|
||||
msgid "The group will be placed in 'pending removal' state"
|
||||
msgid "The group will be placed in 'pending deletion' state"
|
||||
msgstr ""
|
||||
|
||||
msgid "The group_project_ids parameter is only allowed for a group"
|
||||
|
@ -33741,10 +33741,10 @@ msgstr ""
|
|||
msgid "This project path either does not exist or you do not have access."
|
||||
msgstr ""
|
||||
|
||||
msgid "This project will be removed on %{date}"
|
||||
msgid "This project will be deleted on %{date}"
|
||||
msgstr ""
|
||||
|
||||
msgid "This project will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal."
|
||||
msgid "This project will be deleted on %{date} since its parent group '%{parent_group_name}' has been scheduled for deletion."
|
||||
msgstr ""
|
||||
|
||||
msgid "This project will live in your group %{strong_open}%{namespace}%{strong_close}. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more."
|
||||
|
@ -35340,7 +35340,7 @@ msgstr ""
|
|||
msgid "Uploads"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on %{date}. Until that time:"
|
||||
msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently deleted after %{deletion_adjourned_period} days on %{date}. Until that time:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upstream"
|
||||
|
@ -39523,7 +39523,7 @@ msgstr ""
|
|||
msgid "pending comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "pending removal"
|
||||
msgid "pending deletion"
|
||||
msgstr ""
|
||||
|
||||
msgid "per day"
|
||||
|
|
4
qa/qa.rb
4
qa/qa.rb
|
@ -297,6 +297,10 @@ module QA
|
|||
autoload :New, 'qa/page/project/pipeline/new'
|
||||
end
|
||||
|
||||
module PipelineEditor
|
||||
autoload :Show, 'qa/page/project/pipeline_editor/show'
|
||||
end
|
||||
|
||||
module Tag
|
||||
autoload :Index, 'qa/page/project/tag/index'
|
||||
autoload :New, 'qa/page/project/tag/new'
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Page
|
||||
module Project
|
||||
module PipelineEditor
|
||||
class Show < QA::Page::Base
|
||||
view 'app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue' do
|
||||
element :branch_selector_button
|
||||
element :menu_branch_button
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do
|
||||
element :target_branch_field
|
||||
end
|
||||
|
||||
def has_branch_selector_button?
|
||||
has_element? :branch_selector_button
|
||||
end
|
||||
|
||||
def click_branch_selector_button
|
||||
wait_until(reload: false) do
|
||||
has_element?(:branch_selector_button)
|
||||
end
|
||||
click_element(:branch_selector_button, skip_finished_loading_check: true)
|
||||
end
|
||||
|
||||
def select_branch_from_dropdown(branch_to_switch_to)
|
||||
wait_until(reload: false) do
|
||||
has_element?(:menu_branch_button)
|
||||
end
|
||||
click_element(:menu_branch_button, text: branch_to_switch_to, skip_finished_loading_check: true)
|
||||
end
|
||||
|
||||
def target_branch_name
|
||||
wait_until(reload: false) do
|
||||
has_element?(:target_branch_field)
|
||||
end
|
||||
find_element(:target_branch_field, skip_finished_loading_check: true).value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,6 +20,24 @@ module QA
|
|||
click_element(:sidebar_menu_link, menu_item: 'CI/CD')
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_pipeline_editor
|
||||
hover_ci_cd_pipelines do
|
||||
within_submenu do
|
||||
click_element(:sidebar_menu_item_link, menu_item: 'Editor')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hover_ci_cd_pipelines
|
||||
within_sidebar do
|
||||
find_element(:sidebar_menu_link, menu_item: 'CI/CD').hover
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Verify' do
|
||||
describe 'Pipeline editor', :requires_admin do
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'pipeline-editor-project'
|
||||
end
|
||||
end
|
||||
|
||||
let!(:commit) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
commit.project = project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files(
|
||||
[
|
||||
{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content: default_file_content
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
let!(:production_push) do
|
||||
Resource::Repository::Push.fabricate! do |push|
|
||||
push.repository_http_uri = project.repository_http_location.uri
|
||||
push.branch_name = 'production'
|
||||
push.file_name = '.gitlab-ci.yml'
|
||||
push.file_content = production_file_content
|
||||
end
|
||||
end
|
||||
|
||||
let(:default_file_content) do
|
||||
<<~YAML
|
||||
stages:
|
||||
- test
|
||||
|
||||
initialize:
|
||||
stage: test
|
||||
script:
|
||||
- echo "initialized in #{project.default_branch}"
|
||||
YAML
|
||||
end
|
||||
|
||||
let(:production_file_content) do
|
||||
<<~YAML
|
||||
stages:
|
||||
- test
|
||||
|
||||
initialize:
|
||||
stage: test
|
||||
script:
|
||||
- echo "initialized in production"
|
||||
YAML
|
||||
end
|
||||
|
||||
before do
|
||||
Runtime::Feature.enable(:pipeline_editor_branch_switcher)
|
||||
Flow::Login.sign_in
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:go_to_pipeline_editor)
|
||||
end
|
||||
|
||||
after do
|
||||
Runtime::Feature.disable(:pipeline_editor_branch_switcher)
|
||||
project.remove_via_api!
|
||||
Page::Main::Menu.perform(&:sign_out)
|
||||
end
|
||||
|
||||
it 'can switch branches and target branch field updates accordingly', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1856' do
|
||||
Page::Project::PipelineEditor::Show.perform do |show|
|
||||
expect(show).to have_branch_selector_button
|
||||
|
||||
show.click_branch_selector_button
|
||||
show.select_branch_from_dropdown(production_push.branch_name)
|
||||
|
||||
expect(show.target_branch_name).to eq(production_push.branch_name)
|
||||
|
||||
show.click_branch_selector_button
|
||||
show.select_branch_from_dropdown(project.default_branch)
|
||||
|
||||
expect(show.target_branch_name).to eq(project.default_branch)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
|
||||
import IssueBoardFilteredSpec from '~/boards/components/issue_board_filtered_search.vue';
|
||||
import { BoardType } from '~/boards/constants';
|
||||
import issueBoardFilters from '~/boards/issue_board_filters';
|
||||
import { mockTokens } from '../mock_data';
|
||||
|
||||
describe('IssueBoardFilter', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = ({ initialFilterParams = {} } = {}) => {
|
||||
wrapper = shallowMount(IssueBoardFilteredSpec, {
|
||||
provide: { initialFilterParams },
|
||||
props: { fullPath: '', boardType: '' },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('default', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('finds BoardFilteredSearch', () => {
|
||||
expect(wrapper.find(BoardFilteredSearch).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each([[BoardType.group], [BoardType.project]])(
|
||||
'when boardType is %s we pass the correct tokens to BoardFilteredSearch',
|
||||
(boardType) => {
|
||||
const { fetchAuthors, fetchLabels } = issueBoardFilters({}, '', boardType);
|
||||
|
||||
const tokens = mockTokens(fetchLabels, fetchAuthors);
|
||||
|
||||
expect(wrapper.find(BoardFilteredSearch).props('tokens').toString()).toBe(
|
||||
tokens.toString(),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -5,6 +5,9 @@ import Vue from 'vue';
|
|||
import '~/boards/models/list';
|
||||
import { ListType } from '~/boards/constants';
|
||||
import boardsStore from '~/boards/stores/boards_store';
|
||||
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';
|
||||
|
||||
export const boardObj = {
|
||||
id: 1,
|
||||
|
@ -526,3 +529,44 @@ export const mockMoveData = {
|
|||
originalIssue: { foo: 'bar' },
|
||||
...mockMoveIssueParams,
|
||||
};
|
||||
|
||||
export const mockTokens = (fetchLabels, fetchAuthors) => [
|
||||
{
|
||||
icon: 'labels',
|
||||
title: __('Label'),
|
||||
type: 'label_name',
|
||||
operators: [
|
||||
{ value: '=', description: 'is' },
|
||||
{ value: '!=', description: 'is not' },
|
||||
],
|
||||
token: LabelToken,
|
||||
unique: false,
|
||||
symbol: '~',
|
||||
fetchLabels,
|
||||
},
|
||||
{
|
||||
icon: 'pencil',
|
||||
title: __('Author'),
|
||||
type: 'author_username',
|
||||
operators: [
|
||||
{ value: '=', description: 'is' },
|
||||
{ value: '!=', description: 'is not' },
|
||||
],
|
||||
symbol: '@',
|
||||
token: AuthorToken,
|
||||
unique: true,
|
||||
fetchAuthors,
|
||||
},
|
||||
{
|
||||
icon: 'user',
|
||||
title: __('Assignee'),
|
||||
type: 'assignee_username',
|
||||
operators: [
|
||||
{ value: '=', description: 'is' },
|
||||
{ value: '!=', description: 'is not' },
|
||||
],
|
||||
token: AuthorToken,
|
||||
unique: true,
|
||||
fetchAuthors,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -162,11 +162,11 @@ describe('GroupItemComponent', () => {
|
|||
wrapper = createComponent({ group });
|
||||
});
|
||||
|
||||
it('renders the group pending removal badge', () => {
|
||||
it('renders the group pending deletion badge', () => {
|
||||
const badgeEl = wrapper.vm.$el.querySelector('.badge-warning');
|
||||
|
||||
expect(badgeEl).toBeDefined();
|
||||
expect(badgeEl.innerHTML).toContain('pending removal');
|
||||
expect(badgeEl.innerHTML).toContain('pending deletion');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -176,10 +176,10 @@ describe('GroupItemComponent', () => {
|
|||
wrapper = createComponent({ group });
|
||||
});
|
||||
|
||||
it('does not render the group pending removal badge', () => {
|
||||
it('does not render the group pending deletion badge', () => {
|
||||
const groupTextContainer = wrapper.vm.$el.querySelector('.group-text-container');
|
||||
|
||||
expect(groupTextContainer).not.toContain('pending removal');
|
||||
expect(groupTextContainer).not.toContain('pending deletion');
|
||||
});
|
||||
|
||||
it('renders `item-actions` component and passes correct props to it', () => {
|
||||
|
|
|
@ -34,10 +34,10 @@ describe('Fork groups list item component', () => {
|
|||
});
|
||||
};
|
||||
|
||||
it('renders pending removal badge if applicable', () => {
|
||||
it('renders pending deletion badge if applicable', () => {
|
||||
createWrapper({ group: { ...DEFAULT_GROUP_DATA, marked_for_deletion: true } });
|
||||
|
||||
expect(wrapper.find(GlBadge).text()).toBe('pending removal');
|
||||
expect(wrapper.find(GlBadge).text()).toBe('pending deletion');
|
||||
});
|
||||
|
||||
it('renders go to fork button if has forked project', () => {
|
||||
|
|
|
@ -293,6 +293,8 @@ RSpec.configure do |config|
|
|||
# As we're ready to change `master` usages to `main`, let's enable it
|
||||
stub_feature_flags(main_branch_over_master: false)
|
||||
|
||||
stub_feature_flags(issue_boards_filtered_search: false)
|
||||
|
||||
# Disable issue respositioning to avoid heavy load on database when importing big projects.
|
||||
# This is only turned on when app is handling heavy project imports.
|
||||
# Can be removed when we find a better way to deal with the problem.
|
||||
|
|
Loading…
Reference in New Issue