mirror of
https://github.com/gitlabhq/gitlabhq.git
synced 2025-08-10 03:00:46 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -232,10 +232,10 @@ export const getFilterTokens = (locationSearch) =>
|
||||
};
|
||||
});
|
||||
|
||||
const isNotEmptySearchToken = (token) =>
|
||||
export const isNotEmptySearchToken = (token) =>
|
||||
!(token.type === FILTERED_SEARCH_TERM && !token.value.data);
|
||||
|
||||
const isSpecialFilter = (type, data) => {
|
||||
export const isSpecialFilter = (type, data) => {
|
||||
const isAssigneeIdParam =
|
||||
type === TOKEN_TYPE_ASSIGNEE &&
|
||||
isPositiveInteger(data) &&
|
||||
|
@ -2,15 +2,38 @@
|
||||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import { isPositiveInteger } from '~/lib/utils/number_utils';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { getParameterByName } from '~/lib/utils/url_utility';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
||||
import { issuableListTabs } from '~/vue_shared/issuable/list/constants';
|
||||
import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
|
||||
import {
|
||||
convertToSearchQuery,
|
||||
convertToApiParams,
|
||||
getInitialPageParams,
|
||||
getFilterTokens,
|
||||
isSortKey,
|
||||
} from '~/issues/list/utils';
|
||||
import {
|
||||
OPERATORS_IS_NOT,
|
||||
OPERATORS_IS_NOT_OR,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import {
|
||||
MAX_LIST_SIZE,
|
||||
ISSUE_REFERENCE,
|
||||
PARAM_STATE,
|
||||
PARAM_FIRST_PAGE_SIZE,
|
||||
PARAM_LAST_PAGE_SIZE,
|
||||
PARAM_PAGE_AFTER,
|
||||
PARAM_PAGE_BEFORE,
|
||||
PARAM_SORT,
|
||||
CREATED_DESC,
|
||||
UPDATED_DESC,
|
||||
urlSortParams,
|
||||
} from '~/issues/list/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_USER } from '~/graphql_shared/constants';
|
||||
import searchUsersQuery from '~/issues/list/queries/search_users.query.graphql';
|
||||
@ -24,12 +47,12 @@ import {
|
||||
noSearchNoFilterTitle,
|
||||
searchPlaceholder,
|
||||
SERVICE_DESK_BOT_USERNAME,
|
||||
MAX_LIST_SIZE,
|
||||
STATUS_OPEN,
|
||||
STATUS_CLOSED,
|
||||
STATUS_ALL,
|
||||
WORKSPACE_PROJECT,
|
||||
} from '../constants';
|
||||
import { convertToUrlParams } from '../utils';
|
||||
import {
|
||||
searchWithinTokenBase,
|
||||
assigneeTokenBase,
|
||||
@ -68,6 +91,7 @@ export default {
|
||||
'fullPath',
|
||||
'isServiceDeskSupported',
|
||||
'hasAnyIssues',
|
||||
'initialSort',
|
||||
],
|
||||
props: {
|
||||
eeSearchTokens: {
|
||||
@ -81,7 +105,12 @@ export default {
|
||||
serviceDeskIssues: [],
|
||||
serviceDeskIssuesCounts: {},
|
||||
sortOptions: [],
|
||||
filterTokens: [],
|
||||
pageInfo: {},
|
||||
pageParams: {},
|
||||
sortKey: CREATED_DESC,
|
||||
state: STATUS_OPEN,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
issuesError: null,
|
||||
};
|
||||
},
|
||||
@ -109,7 +138,7 @@ export default {
|
||||
Sentry.captureException(error);
|
||||
},
|
||||
skip() {
|
||||
return !this.hasAnyIssues;
|
||||
return this.shouldSkipQuery;
|
||||
},
|
||||
},
|
||||
serviceDeskIssuesCounts: {
|
||||
@ -124,6 +153,9 @@ export default {
|
||||
this.issuesError = this.$options.i18n.errorFetchingCounts;
|
||||
Sentry.captureException(error);
|
||||
},
|
||||
skip() {
|
||||
return this.shouldSkipQuery;
|
||||
},
|
||||
context: {
|
||||
isSingleRequest: true,
|
||||
},
|
||||
@ -131,14 +163,23 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
queryVariables() {
|
||||
const isIidSearch = ISSUE_REFERENCE.test(this.searchQuery);
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
iid: isIidSearch ? this.searchQuery.slice(1) : undefined,
|
||||
isProject: this.isProject,
|
||||
isSignedIn: this.isSignedIn,
|
||||
authorUsername: SERVICE_DESK_BOT_USERNAME,
|
||||
sort: this.sortKey,
|
||||
state: this.state,
|
||||
...this.pageParams,
|
||||
...this.apiFilterParams,
|
||||
search: isIidSearch ? undefined : this.searchQuery,
|
||||
};
|
||||
},
|
||||
shouldSkipQuery() {
|
||||
return !this.hasAnyIssues || isEmpty(this.pageParams);
|
||||
},
|
||||
tabCounts() {
|
||||
const { openedIssues, closedIssues, allIssues } = this.serviceDeskIssuesCounts;
|
||||
return {
|
||||
@ -147,12 +188,40 @@ export default {
|
||||
[STATUS_ALL]: allIssues?.count,
|
||||
};
|
||||
},
|
||||
urlParams() {
|
||||
return {
|
||||
sort: urlSortParams[this.sortKey],
|
||||
state: this.state,
|
||||
...this.urlFilterParams,
|
||||
first_page_size: this.pageParams.firstPageSize,
|
||||
last_page_size: this.pageParams.lastPageSize,
|
||||
page_after: this.pageParams.afterCursor ?? undefined,
|
||||
page_before: this.pageParams.beforeCursor ?? undefined,
|
||||
};
|
||||
},
|
||||
isInfoBannerVisible() {
|
||||
return this.isServiceDeskSupported && this.hasAnyIssues;
|
||||
},
|
||||
hasOrFeature() {
|
||||
return this.glFeatures.orIssuableQueries;
|
||||
},
|
||||
hasSearch() {
|
||||
return Boolean(
|
||||
this.searchQuery ||
|
||||
Object.keys(this.urlFilterParams).length ||
|
||||
this.pageParams.afterCursor ||
|
||||
this.pageParams.beforeCursor,
|
||||
);
|
||||
},
|
||||
apiFilterParams() {
|
||||
return convertToApiParams(this.filterTokens);
|
||||
},
|
||||
urlFilterParams() {
|
||||
return convertToUrlParams(this.filterTokens);
|
||||
},
|
||||
searchQuery() {
|
||||
return convertToSearchQuery(this.filterTokens);
|
||||
},
|
||||
searchTokens() {
|
||||
const preloadedUsers = [];
|
||||
|
||||
@ -219,7 +288,15 @@ export default {
|
||||
return tokens;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(newValue, oldValue) {
|
||||
if (newValue.fullPath !== oldValue.fullPath) {
|
||||
this.updateData(getParameterByName(PARAM_SORT));
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.updateData(this.initialSort);
|
||||
this.cache = {};
|
||||
},
|
||||
methods: {
|
||||
@ -287,6 +364,37 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.state = state;
|
||||
this.pageParams = getInitialPageParams(this.pageSize);
|
||||
|
||||
this.$router.push({ query: this.urlParams });
|
||||
},
|
||||
handleFilter(tokens) {
|
||||
this.filterTokens = tokens;
|
||||
this.pageParams = getInitialPageParams(this.pageSize);
|
||||
|
||||
this.$router.push({ query: this.urlParams });
|
||||
},
|
||||
updateData(sortValue) {
|
||||
const firstPageSize = getParameterByName(PARAM_FIRST_PAGE_SIZE);
|
||||
const lastPageSize = getParameterByName(PARAM_LAST_PAGE_SIZE);
|
||||
const state = getParameterByName(PARAM_STATE);
|
||||
|
||||
const defaultSortKey = state === STATUS_CLOSED ? UPDATED_DESC : CREATED_DESC;
|
||||
const graphQLSortKey = isSortKey(sortValue?.toUpperCase()) && sortValue.toUpperCase();
|
||||
|
||||
const sortKey = graphQLSortKey || defaultSortKey;
|
||||
|
||||
this.filterTokens = getFilterTokens(window.location.search);
|
||||
|
||||
this.pageParams = getInitialPageParams(
|
||||
this.pageSize,
|
||||
isPositiveInteger(firstPageSize) ? parseInt(firstPageSize, 10) : undefined,
|
||||
isPositiveInteger(lastPageSize) ? parseInt(lastPageSize, 10) : undefined,
|
||||
getParameterByName(PARAM_PAGE_AFTER),
|
||||
getParameterByName(PARAM_PAGE_BEFORE),
|
||||
);
|
||||
this.sortKey = sortKey;
|
||||
this.state = state || STATUS_OPEN;
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -297,16 +405,22 @@ export default {
|
||||
<info-banner v-if="isInfoBannerVisible" />
|
||||
<issuable-list
|
||||
namespace="service-desk"
|
||||
recent-searches-storage-key="issues"
|
||||
recent-searches-storage-key="service-desk-issues"
|
||||
:error="issuesError"
|
||||
:search-input-placeholder="$options.i18n.searchPlaceholder"
|
||||
:search-tokens="searchTokens"
|
||||
:initial-filter-value="filterTokens"
|
||||
:show-filtered-search-friendly-text="hasOrFeature"
|
||||
:sort-options="sortOptions"
|
||||
:initial-sort-by="sortKey"
|
||||
:issuables="serviceDeskIssues"
|
||||
:tabs="$options.issuableListTabs"
|
||||
:tab-counts="tabCounts"
|
||||
:current-tab="state"
|
||||
:default-page-size="pageSize"
|
||||
sync-filter-and-sort
|
||||
@click-tab="handleClickTab"
|
||||
@filter="handleFilter"
|
||||
>
|
||||
<template #empty-state>
|
||||
<gl-empty-state
|
||||
|
@ -1,12 +1,231 @@
|
||||
import { __, s__ } from '~/locale';
|
||||
import {
|
||||
FILTERED_SEARCH_TERM,
|
||||
OPERATOR_IS,
|
||||
OPERATOR_NOT,
|
||||
OPERATOR_OR,
|
||||
TOKEN_TYPE_ASSIGNEE,
|
||||
TOKEN_TYPE_CONFIDENTIAL,
|
||||
TOKEN_TYPE_EPIC,
|
||||
TOKEN_TYPE_HEALTH,
|
||||
TOKEN_TYPE_ITERATION,
|
||||
TOKEN_TYPE_LABEL,
|
||||
TOKEN_TYPE_MILESTONE,
|
||||
TOKEN_TYPE_MY_REACTION,
|
||||
TOKEN_TYPE_RELEASE,
|
||||
TOKEN_TYPE_TYPE,
|
||||
TOKEN_TYPE_WEIGHT,
|
||||
TOKEN_TYPE_SEARCH_WITHIN,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import {
|
||||
ALTERNATIVE_FILTER,
|
||||
API_PARAM,
|
||||
NORMAL_FILTER,
|
||||
SPECIAL_FILTER,
|
||||
URL_PARAM,
|
||||
} from '~/issues/list/constants';
|
||||
|
||||
export const SERVICE_DESK_BOT_USERNAME = 'support-bot';
|
||||
export const MAX_LIST_SIZE = 10;
|
||||
export const ISSUE_REFERENCE = /^#\d+$/;
|
||||
|
||||
export const STATUS_ALL = 'all';
|
||||
export const STATUS_CLOSED = 'closed';
|
||||
export const STATUS_OPEN = 'opened';
|
||||
|
||||
export const WORKSPACE_PROJECT = 'project';
|
||||
|
||||
export const filtersMap = {
|
||||
[FILTERED_SEARCH_TERM]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'search',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[undefined]: {
|
||||
[NORMAL_FILTER]: 'search',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_SEARCH_WITHIN]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'in',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'in',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_ASSIGNEE]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'assigneeUsernames',
|
||||
[SPECIAL_FILTER]: 'assigneeId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'assignee_username[]',
|
||||
[SPECIAL_FILTER]: 'assignee_id',
|
||||
[ALTERNATIVE_FILTER]: 'assignee_username',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[assignee_username][]',
|
||||
},
|
||||
[OPERATOR_OR]: {
|
||||
[NORMAL_FILTER]: 'or[assignee_username][]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_MILESTONE]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'milestoneTitle',
|
||||
[SPECIAL_FILTER]: 'milestoneWildcardId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'milestone_title',
|
||||
[SPECIAL_FILTER]: 'milestone_title',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[milestone_title]',
|
||||
[SPECIAL_FILTER]: 'not[milestone_title]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_LABEL]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'labelName',
|
||||
[SPECIAL_FILTER]: 'labelName',
|
||||
[ALTERNATIVE_FILTER]: 'labelNames',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'label_name[]',
|
||||
[SPECIAL_FILTER]: 'label_name[]',
|
||||
[ALTERNATIVE_FILTER]: 'label_name',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[label_name][]',
|
||||
},
|
||||
[OPERATOR_OR]: {
|
||||
[ALTERNATIVE_FILTER]: 'or[label_name][]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_TYPE]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'types',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'type[]',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[type][]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_RELEASE]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'releaseTag',
|
||||
[SPECIAL_FILTER]: 'releaseTagWildcardId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'release_tag',
|
||||
[SPECIAL_FILTER]: 'release_tag',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[release_tag]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_MY_REACTION]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'myReactionEmoji',
|
||||
[SPECIAL_FILTER]: 'myReactionEmoji',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'my_reaction_emoji',
|
||||
[SPECIAL_FILTER]: 'my_reaction_emoji',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[my_reaction_emoji]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_CONFIDENTIAL]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'confidential',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'confidential',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_ITERATION]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'iterationId',
|
||||
[SPECIAL_FILTER]: 'iterationWildcardId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'iteration_id',
|
||||
[SPECIAL_FILTER]: 'iteration_id',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[iteration_id]',
|
||||
[SPECIAL_FILTER]: 'not[iteration_id]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_EPIC]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'epicId',
|
||||
[SPECIAL_FILTER]: 'epicId',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'epic_id',
|
||||
[SPECIAL_FILTER]: 'epic_id',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[epic_id]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_WEIGHT]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'weight',
|
||||
[SPECIAL_FILTER]: 'weight',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'weight',
|
||||
[SPECIAL_FILTER]: 'weight',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[weight]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_HEALTH]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'healthStatusFilter',
|
||||
[SPECIAL_FILTER]: 'healthStatusFilter',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'health_status',
|
||||
[SPECIAL_FILTER]: 'health_status',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[health_status]',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const errorFetchingCounts = __('An error occurred while getting issue counts');
|
||||
export const errorFetchingIssues = __('An error occurred while loading issues');
|
||||
export const noSearchNoFilterTitle = __('Please select at least one filter to see results');
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import VueRouter from 'vue-router';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import ServiceDeskListApp from 'ee_else_ce/service_desk/components/service_desk_list_app.vue';
|
||||
import { gqlClient } from './graphql';
|
||||
@ -23,6 +24,7 @@ export async function mountServiceDeskListApp() {
|
||||
projectDataIsProject,
|
||||
projectDataIsSignedIn,
|
||||
projectDataHasAnyIssues,
|
||||
projectDataInitialSort,
|
||||
serviceDeskEmailAddress,
|
||||
canAdminIssues,
|
||||
canEditProjectSettings,
|
||||
@ -34,6 +36,7 @@ export async function mountServiceDeskListApp() {
|
||||
} = el.dataset;
|
||||
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(VueRouter);
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
@ -41,6 +44,11 @@ export async function mountServiceDeskListApp() {
|
||||
apolloProvider: new VueApollo({
|
||||
defaultClient: await gqlClient(),
|
||||
}),
|
||||
router: new VueRouter({
|
||||
base: window.location.pathname,
|
||||
mode: 'history',
|
||||
routes: [{ path: '/' }],
|
||||
}),
|
||||
provide: {
|
||||
releasesPath: projectDataReleasesPath,
|
||||
autocompleteAwardEmojisPath: projectDataAutocompleteAwardEmojisPath,
|
||||
@ -61,6 +69,7 @@ export async function mountServiceDeskListApp() {
|
||||
isServiceDeskSupported: parseBoolean(isServiceDeskSupported),
|
||||
isServiceDeskEnabled: parseBoolean(isServiceDeskEnabled),
|
||||
hasAnyIssues: parseBoolean(projectDataHasAnyIssues),
|
||||
initialSort: projectDataInitialSort,
|
||||
},
|
||||
render: (createComponent) => createComponent(ServiceDeskListApp),
|
||||
});
|
||||
|
37
app/assets/javascripts/service_desk/utils.js
Normal file
37
app/assets/javascripts/service_desk/utils.js
Normal file
@ -0,0 +1,37 @@
|
||||
import {
|
||||
OPERATOR_OR,
|
||||
TOKEN_TYPE_LABEL,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { isSpecialFilter, isNotEmptySearchToken } from '~/issues/list/utils';
|
||||
import {
|
||||
ALTERNATIVE_FILTER,
|
||||
NORMAL_FILTER,
|
||||
SPECIAL_FILTER,
|
||||
URL_PARAM,
|
||||
} from '~/issues/list/constants';
|
||||
import { filtersMap } from './constants';
|
||||
|
||||
const getFilterType = ({ type, value: { data, operator } }) => {
|
||||
const isUnionedLabel = type === TOKEN_TYPE_LABEL && operator === OPERATOR_OR;
|
||||
|
||||
if (isUnionedLabel) {
|
||||
return ALTERNATIVE_FILTER;
|
||||
}
|
||||
if (isSpecialFilter(type, data)) {
|
||||
return SPECIAL_FILTER;
|
||||
}
|
||||
return NORMAL_FILTER;
|
||||
};
|
||||
|
||||
export const convertToUrlParams = (filterTokens) => {
|
||||
const urlParamsMap = filterTokens.filter(isNotEmptySearchToken).reduce((acc, token) => {
|
||||
const filterType = getFilterType(token);
|
||||
const urlParam = filtersMap[token.type][URL_PARAM][token.value.operator]?.[filterType];
|
||||
return acc.set(
|
||||
urlParam,
|
||||
acc.has(urlParam) ? [acc.get(urlParam), token.value.data].flat() : token.value.data,
|
||||
);
|
||||
}, new Map());
|
||||
|
||||
return Object.fromEntries(urlParamsMap);
|
||||
};
|
@ -41,7 +41,7 @@ To enable the agent server on a single node:
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
|
||||
|
||||
For additional configuration options, see the **Enable GitLab KAS** section of the
|
||||
[`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-config-template/gitlab.rb.template).
|
||||
[`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/be52c36c243a3422ec38b7d45d459682a07e195f/files/gitlab-config-template/gitlab.rb.template#L1951).
|
||||
|
||||
##### Configure KAS to listen on a UNIX socket
|
||||
|
||||
|
@ -236,37 +236,61 @@ The project for a new Gem should always be created in [`gitlab-org/ruby/gems` na
|
||||
|
||||
1. Determine a suitable name for the gem. If it's a GitLab-owned gem, prefix
|
||||
the gem name with `gitlab-`. For example, `gitlab-sidekiq-fetcher`.
|
||||
1. Create the gem or fork as necessary.
|
||||
1. Ensure the `gitlab_rubygems` group is an owner of the new gem by running:
|
||||
1. Locally create the gem or fork as necessary.
|
||||
1. [Publish an empty `0.0.1` version of the gem to rubygems.org](https://guides.rubygems.org/publishing/#publishing-to-rubygemsorg) to ensure the gem name is reserved.
|
||||
1. Add the [`gitlab_rubygems`](https://rubygems.org/profiles/gitlab_rubygems) and [`gitlab-qa`](https://rubygems.org/profiles/gitlab-qa) users as owners of the new gem by running:
|
||||
|
||||
```shell
|
||||
gem owner <gem-name> --add gitlab_rubygems
|
||||
gem owner <gem-name> --add gitlab-qa
|
||||
```
|
||||
|
||||
1. [Publish the gem to rubygems.org](https://guides.rubygems.org/publishing/#publishing-to-rubygemsorg)
|
||||
1. Visit `https://rubygems.org/gems/<gem-name>` and verify that the gem published
|
||||
successfully and `gitlab_rubygems` is also an owner.
|
||||
1. Create a project in [`gitlab-org/ruby/gems` namespace](https://gitlab.com/gitlab-org/ruby/gems/).
|
||||
1. Optional. Add some or all of the following users as co-owners:
|
||||
- [Marin Jankovski](https://rubygems.org/profiles/marinjankovski)
|
||||
- [Rémy Coutable](https://rubygems.org/profiles/rymai)
|
||||
- [Stan Hu](https://rubygems.org/profiles/stanhu)
|
||||
1. Optional. Add any other relevant developers as co-owners.
|
||||
1. Visit `https://rubygems.org/gems/<gem-name>` and verify that the gem was published
|
||||
successfully and `gitlab_rubygems` & `gitlab-qa` are also owners.
|
||||
1. Create a project in the [`gitlab-org/ruby/gems` group](https://gitlab.com/gitlab-org/ruby/gems/). To create this project:
|
||||
1. Follow the [instructions for new projects](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#creating-a-new-project).
|
||||
1. Follow the instructions for setting up a [CI/CD configuration](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#cicd-configuration).
|
||||
1. Use the [shared CI/CD config](https://gitlab.com/gitlab-org/quality/pipeline-common/-/blob/master/ci/gem-release.yml)
|
||||
to release and publish new gem versions by adding the following to their `.gitlab-ci.yml`:
|
||||
|
||||
- To create this project:
|
||||
1. Follow the [instructions for new projects](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#creating-a-new-project).
|
||||
1. Follow the instructions for setting up a [CI/CD configuration](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#cicd-configuration).
|
||||
1. Follow the instructions for [publishing a project](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#publishing-a-project).
|
||||
- See [issue #325463](https://gitlab.com/gitlab-org/gitlab/-/issues/325463)
|
||||
for an example.
|
||||
- In some cases we may want to move a gem to its own namespace. Some
|
||||
examples might be that it will naturally have more than one project
|
||||
(say, something that has plugins as separate libraries), or that we
|
||||
expect users outside GitLab to be maintainers on this project as
|
||||
well as GitLab team members.
|
||||
```yaml
|
||||
include:
|
||||
- project: 'gitlab-org/quality/pipeline-common'
|
||||
file: '/ci/gem-release.yml'
|
||||
```
|
||||
|
||||
The latter situation (maintainers from outside GitLab) could also
|
||||
apply if someone who currently works at GitLab wants to maintain
|
||||
the gem beyond their time working at GitLab.
|
||||
This job will handle building and publishing the gem (it uses a `gilab-qa` Rubygems.org
|
||||
API token inherited from the `gitlab-org/ruby/gems` group, in order to publish the gem
|
||||
package), as well as creating the tag, release and populating its release notes by
|
||||
using the
|
||||
[Generate changelog data](../api/repositories.md#generate-changelog-data)
|
||||
API endpoint.
|
||||
|
||||
When publishing a gem to RubyGems.org, also note the section on
|
||||
[gem owners](https://about.gitlab.com/handbook/developer-onboarding/#ruby-gems)
|
||||
in the handbook.
|
||||
For instructions for when and how to generate a changelog entry file, see the
|
||||
dedicated [Changelog entries](changelog.md)
|
||||
page.
|
||||
[To be consistent with the GitLab project](changelog.md),
|
||||
Gem projects could also define a changelog YAML configuration file at
|
||||
`.gitlab/changelog_config.yml` with the same content
|
||||
as [in the `gitlab-styles` gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/blob/master/.gitlab/changelog_config.yml).
|
||||
1. To ease the release process, you could also create a `.gitlab/merge_request_templates/Release.md` MR template with the same content
|
||||
as [in the `gitlab-styles` gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/raw/master/.gitlab/merge_request_templates/Release.md)
|
||||
(make sure to replace `gitlab-styles` with the actual gem name).
|
||||
1. Follow the instructions for [publishing a project](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#publishing-a-project).
|
||||
|
||||
Notes: In some cases we may want to move a gem to its own namespace. Some
|
||||
examples might be that it will naturally have more than one project
|
||||
(say, something that has plugins as separate libraries), or that we
|
||||
expect users outside GitLab to be maintainers on this project as
|
||||
well as GitLab team members.
|
||||
The latter situation (maintainers from outside GitLab) could also
|
||||
apply if someone who currently works at GitLab wants to maintain
|
||||
the gem beyond their time working at GitLab.
|
||||
|
||||
## The `vendor/gems/`
|
||||
|
||||
|
@ -1,14 +1,16 @@
|
||||
---
|
||||
stage: Deploy
|
||||
group: Environments
|
||||
info: A tutorial for structuring a repository for GitOps deployments
|
||||
info: A tutorial for deploying a GitLab repository using Flux
|
||||
---
|
||||
|
||||
# Tutorial: Structure your repository for GitOps deployments **(FREE)**
|
||||
# Tutorial: Deploy a Git repository using Flux **(FREE)**
|
||||
|
||||
In this tutorial, you'll create a GitLab project that builds and deploys an application
|
||||
to a Kubernetes cluster using Flux. You'll set up a sample manifest project, configure it to
|
||||
push manifests to a deployment branch, and configure Flux to sync the deployment branch.
|
||||
push manifests to a deployment branch, and configure Flux to sync the deployment branch. With this
|
||||
setup, you can run additional steps in GitLab pipelines before Flux picks up the changes
|
||||
from the repository.
|
||||
|
||||
This tutorial deploys an application from a public project. If you want to add a non-public project, you should create a [project deploy token](../../../project/deploy_tokens/index.md).
|
||||
|
||||
|
@ -8,7 +8,9 @@ info: A tutorial for deploying an OCI artifact using Flux
|
||||
|
||||
This tutorial teaches you how to package your Kubernetes manifests into an [OCI](https://opencontainers.org/)
|
||||
artifact and deploy them to your cluster using Flux. You'll set up a sample manifest project, configure it to
|
||||
store manifests as an artifact in the project's Container Registry, and configure Flux to sync the artifact.
|
||||
store manifests as an artifact in the project's Container Registry, and configure Flux to sync the artifact. With this
|
||||
setup, you can run additional steps in GitLab pipelines before Flux picks up the changes
|
||||
from the OCI image.
|
||||
|
||||
This tutorial deploys an application from a public project. If you want to add a non-public project, you should create a [project deploy token](../../../project/deploy_tokens/index.md).
|
||||
|
||||
|
@ -124,7 +124,7 @@
|
||||
"clipboard": "^2.0.8",
|
||||
"compression-webpack-plugin": "^5.0.2",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"core-js": "^3.31.1",
|
||||
"core-js": "^3.32.0",
|
||||
"cron-validator": "^1.1.1",
|
||||
"cronstrue": "^1.122.0",
|
||||
"cropper": "^2.3.0",
|
||||
@ -202,8 +202,8 @@
|
||||
"tippy.js": "^6.3.7",
|
||||
"traverse": "^0.6.7",
|
||||
"unified": "^10.1.2",
|
||||
"unist-builder": "^3.0.1",
|
||||
"unist-util-visit-parents": "^5.1.3",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unist-util-visit-parents": "5.1.3",
|
||||
"url-loader": "^4.1.1",
|
||||
"uuid": "8.1.0",
|
||||
"visibilityjs": "^1.2.4",
|
||||
|
@ -2,14 +2,17 @@ import { shallowMount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import VueRouter from 'vue-router';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
||||
import { issuableListTabs } from '~/vue_shared/issuable/list/constants';
|
||||
import { TYPENAME_USER } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { STATUS_CLOSED, STATUS_OPEN } from '~/service_desk/constants';
|
||||
import { STATUS_CLOSED, STATUS_OPEN, STATUS_ALL } from '~/service_desk/constants';
|
||||
import getServiceDeskIssuesQuery from 'ee_else_ce/service_desk/queries/get_service_desk_issues.query.graphql';
|
||||
import getServiceDeskIssuesCountsQuery from 'ee_else_ce/service_desk/queries/get_service_desk_issues_counts.query.graphql';
|
||||
import ServiceDeskListApp from '~/service_desk/components/service_desk_list_app.vue';
|
||||
@ -27,14 +30,19 @@ import {
|
||||
import {
|
||||
getServiceDeskIssuesQueryResponse,
|
||||
getServiceDeskIssuesCountsQueryResponse,
|
||||
filteredTokens,
|
||||
urlParams,
|
||||
locationSearch,
|
||||
} from '../mock_data';
|
||||
|
||||
jest.mock('@sentry/browser');
|
||||
|
||||
describe('ServiceDeskListApp', () => {
|
||||
describe('CE ServiceDeskListApp', () => {
|
||||
let wrapper;
|
||||
let router;
|
||||
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const defaultProvide = {
|
||||
releasesPath: 'releases/path',
|
||||
@ -49,6 +57,7 @@ describe('ServiceDeskListApp', () => {
|
||||
fullPath: 'path/to/project',
|
||||
isServiceDeskSupported: true,
|
||||
hasAnyIssues: true,
|
||||
initialSort: '',
|
||||
};
|
||||
|
||||
let defaultQueryResponse = getServiceDeskIssuesQueryResponse;
|
||||
@ -82,6 +91,8 @@ describe('ServiceDeskListApp', () => {
|
||||
[getServiceDeskIssuesCountsQuery, serviceDeskIssuesCountsQueryResponseHandler],
|
||||
];
|
||||
|
||||
router = new VueRouter({ mode: 'history' });
|
||||
|
||||
return shallowMount(ServiceDeskListApp, {
|
||||
apolloProvider: createMockApollo(
|
||||
requestHandlers,
|
||||
@ -98,6 +109,7 @@ describe('ServiceDeskListApp', () => {
|
||||
},
|
||||
},
|
||||
),
|
||||
router,
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
...provide,
|
||||
@ -106,6 +118,7 @@ describe('ServiceDeskListApp', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setWindowLocation(TEST_HOST);
|
||||
wrapper = createComponent();
|
||||
return waitForPromises();
|
||||
});
|
||||
@ -113,7 +126,7 @@ describe('ServiceDeskListApp', () => {
|
||||
it('fetches service desk issues and renders them in the issuable list', () => {
|
||||
expect(findIssuableList().props()).toMatchObject({
|
||||
namespace: 'service-desk',
|
||||
recentSearchesStorageKey: 'issues',
|
||||
recentSearchesStorageKey: 'service-desk-issues',
|
||||
issuables: defaultQueryResponse.data.project.issues.nodes,
|
||||
tabs: issuableListTabs,
|
||||
currentTab: STATUS_OPEN,
|
||||
@ -145,6 +158,36 @@ describe('ServiceDeskListApp', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initial url params', () => {
|
||||
describe('search', () => {
|
||||
it('is set from the url params', () => {
|
||||
setWindowLocation(locationSearch);
|
||||
wrapper = createComponent();
|
||||
|
||||
expect(router.history.current.query).toMatchObject({ search: 'find issues' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('state', () => {
|
||||
it('is set from the url params', () => {
|
||||
const initialState = STATUS_ALL;
|
||||
setWindowLocation(`?state=${initialState}`);
|
||||
wrapper = createComponent();
|
||||
|
||||
expect(findIssuableList().props('currentTab')).toBe(initialState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filter tokens', () => {
|
||||
it('are set from the url params', () => {
|
||||
setWindowLocation(locationSearch);
|
||||
wrapper = createComponent();
|
||||
|
||||
expect(findIssuableList().props('initialFilterValue')).toEqual(filteredTokens);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tokens', () => {
|
||||
const mockCurrentUser = {
|
||||
id: 1,
|
||||
@ -200,14 +243,36 @@ describe('ServiceDeskListApp', () => {
|
||||
|
||||
describe('Events', () => {
|
||||
describe('when "click-tab" event is emitted by IssuableList', () => {
|
||||
it('updates ui to the new tab', async () => {
|
||||
createComponent();
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
router.push = jest.fn();
|
||||
|
||||
findIssuableList().vm.$emit('click-tab', STATUS_CLOSED);
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
it('updates ui to the new tab', () => {
|
||||
expect(findIssuableList().props('currentTab')).toBe(STATUS_CLOSED);
|
||||
});
|
||||
|
||||
it('updates url to the new tab', () => {
|
||||
expect(router.push).toHaveBeenCalledWith({
|
||||
query: expect.objectContaining({ state: STATUS_CLOSED }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "filter" event is emitted by IssuableList', () => {
|
||||
it('updates IssuableList with url params', async () => {
|
||||
wrapper = createComponent();
|
||||
router.push = jest.fn();
|
||||
|
||||
findIssuableList().vm.$emit('filter', filteredTokens);
|
||||
await nextTick();
|
||||
|
||||
expect(router.push).toHaveBeenCalledWith({
|
||||
query: expect.objectContaining(urlParams),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,3 +1,20 @@
|
||||
import {
|
||||
FILTERED_SEARCH_TERM,
|
||||
OPERATOR_IS,
|
||||
OPERATOR_NOT,
|
||||
OPERATOR_OR,
|
||||
TOKEN_TYPE_ASSIGNEE,
|
||||
TOKEN_TYPE_CONFIDENTIAL,
|
||||
TOKEN_TYPE_EPIC,
|
||||
TOKEN_TYPE_ITERATION,
|
||||
TOKEN_TYPE_LABEL,
|
||||
TOKEN_TYPE_MILESTONE,
|
||||
TOKEN_TYPE_MY_REACTION,
|
||||
TOKEN_TYPE_RELEASE,
|
||||
TOKEN_TYPE_WEIGHT,
|
||||
TOKEN_TYPE_HEALTH,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
|
||||
export const getServiceDeskIssuesQueryResponse = {
|
||||
data: {
|
||||
project: {
|
||||
@ -116,3 +133,104 @@ export const getServiceDeskIssuesCountsQueryResponse = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const filteredTokens = [
|
||||
{ type: FILTERED_SEARCH_TERM, value: { data: 'find issues', operator: 'undefined' } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: 'bart', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: 'lisa', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: '5', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: 'patty', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: 'selma', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: 'carl', operator: OPERATOR_OR } },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, value: { data: 'lenny', operator: OPERATOR_OR } },
|
||||
{ type: TOKEN_TYPE_MILESTONE, value: { data: 'season 3', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_MILESTONE, value: { data: 'season 4', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_MILESTONE, value: { data: 'season 20', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_MILESTONE, value: { data: 'season 30', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'cartoon', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'tv', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'live action', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'drama', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'comedy', operator: OPERATOR_OR } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'sitcom', operator: OPERATOR_OR } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v3', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v4', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v20', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v30', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsup', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsdown', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_CONFIDENTIAL, value: { data: 'yes', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ITERATION, value: { data: '4', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ITERATION, value: { data: '12', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ITERATION, value: { data: '20', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_ITERATION, value: { data: '42', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_EPIC, value: { data: '12', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_EPIC, value: { data: '34', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_WEIGHT, value: { data: '1', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'atRisk', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'onTrack', operator: OPERATOR_NOT } },
|
||||
];
|
||||
|
||||
export const urlParams = {
|
||||
search: 'find issues',
|
||||
'assignee_username[]': ['bart', 'lisa', '5'],
|
||||
'not[assignee_username][]': ['patty', 'selma'],
|
||||
'or[assignee_username][]': ['carl', 'lenny'],
|
||||
milestone_title: ['season 3', 'season 4'],
|
||||
'not[milestone_title]': ['season 20', 'season 30'],
|
||||
'label_name[]': ['cartoon', 'tv'],
|
||||
'not[label_name][]': ['live action', 'drama'],
|
||||
'or[label_name][]': ['comedy', 'sitcom'],
|
||||
release_tag: ['v3', 'v4'],
|
||||
'not[release_tag]': ['v20', 'v30'],
|
||||
my_reaction_emoji: 'thumbsup',
|
||||
'not[my_reaction_emoji]': 'thumbsdown',
|
||||
confidential: 'yes',
|
||||
iteration_id: ['4', '12'],
|
||||
'not[iteration_id]': ['20', '42'],
|
||||
epic_id: '12',
|
||||
'not[epic_id]': '34',
|
||||
weight: '1',
|
||||
'not[weight]': '3',
|
||||
health_status: 'atRisk',
|
||||
'not[health_status]': 'onTrack',
|
||||
};
|
||||
|
||||
export const locationSearch = [
|
||||
'?search=find+issues',
|
||||
'assignee_username[]=bart',
|
||||
'assignee_username[]=lisa',
|
||||
'assignee_username[]=5',
|
||||
'not[assignee_username][]=patty',
|
||||
'not[assignee_username][]=selma',
|
||||
'or[assignee_username][]=carl',
|
||||
'or[assignee_username][]=lenny',
|
||||
'milestone_title=season+3',
|
||||
'milestone_title=season+4',
|
||||
'not[milestone_title]=season+20',
|
||||
'not[milestone_title]=season+30',
|
||||
'label_name[]=cartoon',
|
||||
'label_name[]=tv',
|
||||
'not[label_name][]=live action',
|
||||
'not[label_name][]=drama',
|
||||
'or[label_name][]=comedy',
|
||||
'or[label_name][]=sitcom',
|
||||
'release_tag=v3',
|
||||
'release_tag=v4',
|
||||
'not[release_tag]=v20',
|
||||
'not[release_tag]=v30',
|
||||
'my_reaction_emoji=thumbsup',
|
||||
'not[my_reaction_emoji]=thumbsdown',
|
||||
'confidential=yes',
|
||||
'iteration_id=4',
|
||||
'iteration_id=12',
|
||||
'not[iteration_id]=20',
|
||||
'not[iteration_id]=42',
|
||||
'epic_id=12',
|
||||
'not[epic_id]=34',
|
||||
'weight=1',
|
||||
'not[weight]=3',
|
||||
'health_status=atRisk',
|
||||
'not[health_status]=onTrack',
|
||||
].join('&');
|
||||
|
@ -23,10 +23,12 @@ RSpec.describe Tooling::Danger::ModelValidations, feature_category: :tooling do
|
||||
describe '#add_comment_for_added_validations' do
|
||||
let(:file_lines) { file_diff.map { |line| line.delete_prefix('+').delete_prefix('-') } }
|
||||
let(:filename) { 'app/models/user.rb' }
|
||||
let(:added_filename) { 'app/models/user.rb' }
|
||||
|
||||
before do
|
||||
allow(model_validations.project_helper).to receive(:file_lines).and_return(file_lines)
|
||||
allow(model_validations.helper).to receive(:all_changed_files).and_return([filename])
|
||||
allow(model_validations.helper).to receive(:added_files).and_return([added_filename])
|
||||
allow(model_validations.helper).to receive(:modified_files).and_return([filename])
|
||||
allow(model_validations.helper).to receive(:changed_lines).with(filename).and_return(file_diff)
|
||||
end
|
||||
|
||||
@ -83,11 +85,13 @@ RSpec.describe Tooling::Danger::ModelValidations, feature_category: :tooling do
|
||||
app/models/users/user_follow_user.rb
|
||||
ee/app/models/ee/user.rb
|
||||
ee/app/models/sca/license_policy.rb
|
||||
app/models/concerns/presentable.rb
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
all_new_files = %w[
|
||||
added_files = %w[app/models/user_preferences.rb app/models/concerns/presentable.rb]
|
||||
modified_files = %w[
|
||||
app/models/user.rb
|
||||
app/models/users/user_follow_user.rb
|
||||
ee/app/models/ee/user.rb
|
||||
@ -96,7 +100,8 @@ RSpec.describe Tooling::Danger::ModelValidations, feature_category: :tooling do
|
||||
app/assets/index.js
|
||||
]
|
||||
|
||||
allow(model_validations.helper).to receive(:all_changed_files).and_return(all_new_files)
|
||||
allow(model_validations.helper).to receive(:added_files).and_return(added_files)
|
||||
allow(model_validations.helper).to receive(:modified_files).and_return(modified_files)
|
||||
end
|
||||
|
||||
it 'returns added and modified files' do
|
||||
|
@ -7,8 +7,11 @@ module Tooling
|
||||
module ModelValidations
|
||||
include ::Tooling::Danger::Suggestor
|
||||
|
||||
MODEL_FILES_REGEX = 'app/models'
|
||||
MODEL_FILES_PATH = 'app/models'
|
||||
MODEL_CONCERN_FILES_PATH = 'app/models/concerns'
|
||||
EE_PREFIX = 'ee/'
|
||||
MODEL_FILES_REGEX = %r{\A(#{EE_PREFIX})?#{MODEL_FILES_PATH}}
|
||||
MODEL_CONCERN_FILES_REGEX = %r{\A(#{EE_PREFIX})?#{MODEL_CONCERN_FILES_PATH}}
|
||||
VALIDATION_METHODS = %w[validates validate validates_each validates_with validates_associated].freeze
|
||||
VALIDATIONS_REGEX = /^\+\s*(.*\.)?(#{VALIDATION_METHODS.join('|')})[( ]/
|
||||
|
||||
@ -32,10 +35,10 @@ module Tooling
|
||||
end
|
||||
|
||||
def changed_model_files
|
||||
changed_files = helper.all_changed_files
|
||||
ee_folder_prefix = "(#{EE_PREFIX})?"
|
||||
added_files = helper.added_files
|
||||
modified_files = helper.modified_files
|
||||
|
||||
changed_files.grep(%r{\A#{ee_folder_prefix}#{MODEL_FILES_REGEX}})
|
||||
modified_files.grep(MODEL_FILES_REGEX) + added_files.grep(MODEL_CONCERN_FILES_REGEX)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
40
yarn.lock
40
yarn.lock
@ -2478,7 +2478,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
|
||||
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
||||
|
||||
"@types/unist@*", "@types/unist@^2.0.0":
|
||||
"@types/unist@*", "@types/unist@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a"
|
||||
integrity sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==
|
||||
|
||||
"@types/unist@^2.0.0":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||
@ -4267,10 +4272,10 @@ core-js-pure@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
|
||||
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
|
||||
|
||||
core-js@^3.29.1, core-js@^3.31.1, core-js@^3.6.5:
|
||||
version "3.31.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.1.tgz#f2b0eea9be9da0def2c5fece71064a7e5d687653"
|
||||
integrity sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ==
|
||||
core-js@^3.29.1, core-js@^3.32.0, core-js@^3.6.5:
|
||||
version "3.32.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.0.tgz#7643d353d899747ab1f8b03d2803b0312a0fb3b6"
|
||||
integrity sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.3"
|
||||
@ -12554,13 +12559,20 @@ unique-slug@^2.0.0:
|
||||
dependencies:
|
||||
imurmurhash "^0.1.4"
|
||||
|
||||
unist-builder@^3.0.0, unist-builder@^3.0.1:
|
||||
unist-builder@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.1.tgz#258b89dcadd3c973656b2327b347863556907f58"
|
||||
integrity sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-builder@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-4.0.0.tgz#817b326c015a6f9f5e92bb55b8e8bc5e578fe243"
|
||||
integrity sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
|
||||
unist-util-generated@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113"
|
||||
@ -12585,18 +12597,18 @@ unist-util-stringify-position@^3.0.0:
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-visit-parents@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2"
|
||||
integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==
|
||||
unist-util-visit-parents@5.1.3, unist-util-visit-parents@^5.0.0:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
|
||||
integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
|
||||
unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
|
||||
integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
|
||||
unist-util-visit-parents@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2"
|
||||
integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
|
Reference in New Issue
Block a user