summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/issues/list
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/issues/list')
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue99
-rw-r--r--app/assets/javascripts/issues/list/constants.js6
-rw-r--r--app/assets/javascripts/issues/list/index.js10
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql136
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql94
-rw-r--r--app/assets/javascripts/issues/list/utils.js11
6 files changed, 98 insertions, 258 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 b81ab103271..fa56c0183b2 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -13,8 +13,6 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
-import getIssuesWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_without_crm.query.graphql';
-import getIssuesCountsWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_counts_without_crm.query.graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -23,6 +21,7 @@ import CsvImportExportButtons from '~/issuable/components/csv_import_export_butt
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import { IssuableStatus } from '~/issues/constants';
import axios from '~/lib/utils/axios_utils';
+import { isPositiveInteger } from '~/lib/utils/number_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { getParameterByName, joinPaths } from '~/lib/utils/url_utility';
import {
@@ -31,20 +30,28 @@ import {
TOKEN_TITLE_ASSIGNEE,
TOKEN_TITLE_AUTHOR,
TOKEN_TITLE_CONFIDENTIAL,
+ TOKEN_TITLE_CONTACT,
TOKEN_TITLE_LABEL,
TOKEN_TITLE_MILESTONE,
TOKEN_TITLE_MY_REACTION,
+ TOKEN_TITLE_ORGANIZATION,
TOKEN_TITLE_RELEASE,
TOKEN_TITLE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
+import {
+ IssuableListTabs,
+ IssuableStates,
+ IssuableTypes,
+} from '~/vue_shared/issuable/list/constants';
import {
CREATED_DESC,
i18n,
ISSUE_REFERENCE,
MAX_LIST_SIZE,
PAGE_SIZE,
+ PARAM_FIRST_PAGE_SIZE,
+ PARAM_LAST_PAGE_SIZE,
PARAM_PAGE_AFTER,
PARAM_PAGE_BEFORE,
PARAM_SORT,
@@ -53,9 +60,11 @@ import {
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL,
+ TOKEN_TYPE_CONTACT,
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
+ TOKEN_TYPE_ORGANIZATION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
UPDATED_DESC,
@@ -93,6 +102,7 @@ const ReleaseToken = () =>
export default {
i18n,
IssuableListTabs,
+ IssuableTypes: [IssuableTypes.Issue, IssuableTypes.Incident, IssuableTypes.TestCase],
components: {
CsvImportExportButtons,
GlButton,
@@ -112,6 +122,9 @@ export default {
'autocompleteAwardEmojisPath',
'calendarPath',
'canBulkUpdate',
+ 'canCreateProjects',
+ 'canReadCrmContact',
+ 'canReadCrmOrganization',
'emptyStateSvgPath',
'exportCsvPath',
'fullPath',
@@ -120,6 +133,7 @@ export default {
'hasBlockedIssuesFeature',
'hasIssueWeightsFeature',
'hasMultipleIssueAssigneesFeature',
+ 'hasScopedLabelsFeature',
'initialEmail',
'initialSort',
'isAnonymousSearchDisabled',
@@ -129,6 +143,7 @@ export default {
'isSignedIn',
'jiraIntegrationPath',
'newIssuePath',
+ 'newProjectPath',
'releasesPath',
'rssPath',
'showNewIssueLink',
@@ -157,11 +172,11 @@ export default {
},
apollo: {
issues: {
- query() {
- return this.hasCrmParameter ? getIssuesQuery : getIssuesWithoutCrmQuery;
- },
+ query: getIssuesQuery,
variables() {
- return this.queryVariables;
+ const { types } = this.queryVariables;
+
+ return { ...this.queryVariables, types: types ? [types] : this.$options.IssuableTypes };
},
update(data) {
return data[this.namespace]?.issues.nodes ?? [];
@@ -183,11 +198,11 @@ export default {
debounce: 200,
},
issuesCounts: {
- query() {
- return this.hasCrmParameter ? getIssuesCountsQuery : getIssuesCountsWithoutCrmQuery;
- },
+ query: getIssuesCountsQuery,
variables() {
- return this.queryVariables;
+ const { types } = this.queryVariables;
+
+ return { ...this.queryVariables, types: types ? [types] : this.$options.IssuableTypes };
},
update(data) {
return data[this.namespace] ?? {};
@@ -363,6 +378,28 @@ export default {
});
}
+ if (this.canReadCrmContact) {
+ tokens.push({
+ type: TOKEN_TYPE_CONTACT,
+ title: TOKEN_TITLE_CONTACT,
+ icon: 'user',
+ token: GlFilteredSearchToken,
+ operators: OPERATOR_IS_ONLY,
+ unique: true,
+ });
+ }
+
+ if (this.canReadCrmOrganization) {
+ tokens.push({
+ type: TOKEN_TYPE_ORGANIZATION,
+ title: TOKEN_TITLE_ORGANIZATION,
+ icon: 'users',
+ token: GlFilteredSearchToken,
+ operators: OPERATOR_IS_ONLY,
+ unique: true,
+ });
+ }
+
if (this.eeSearchTokens.length) {
tokens.push(...this.eeSearchTokens);
}
@@ -390,20 +427,16 @@ export default {
},
urlParams() {
return {
- page_after: this.pageParams.afterCursor,
- page_before: this.pageParams.beforeCursor,
search: this.searchQuery,
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,
+ page_before: this.pageParams.beforeCursor,
};
},
- hasCrmParameter() {
- return (
- window.location.search.includes('crm_contact_id=') ||
- window.location.search.includes('crm_organization_id=')
- );
- },
},
watch: {
$route(newValue, oldValue) {
@@ -632,6 +665,8 @@ export default {
this.showBulkEditSidebar = showBulkEditSidebar;
},
updateData(sortValue) {
+ const firstPageSize = getParameterByName(PARAM_FIRST_PAGE_SIZE);
+ const lastPageSize = getParameterByName(PARAM_LAST_PAGE_SIZE);
const pageAfter = getParameterByName(PARAM_PAGE_AFTER);
const pageBefore = getParameterByName(PARAM_PAGE_BEFORE);
const state = getParameterByName(PARAM_STATE);
@@ -660,7 +695,13 @@ export default {
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
this.filterTokens = isSearchDisabled ? [] : getFilterTokens(window.location.search);
- this.pageParams = getInitialPageParams(sortKey, pageAfter, pageBefore);
+ this.pageParams = getInitialPageParams(
+ sortKey,
+ isPositiveInteger(firstPageSize) ? parseInt(firstPageSize, 10) : undefined,
+ isPositiveInteger(lastPageSize) ? parseInt(lastPageSize, 10) : undefined,
+ pageAfter,
+ pageBefore,
+ );
this.sortKey = sortKey;
this.state = state || IssuableStates.Opened;
},
@@ -676,6 +717,7 @@ export default {
recent-searches-storage-key="issues"
:search-input-placeholder="$options.i18n.searchPlaceholder"
:search-tokens="searchTokens"
+ :has-scoped-labels-feature="hasScopedLabelsFeature"
:initial-filter-value="filterTokens"
:sort-options="sortOptions"
:initial-sort-by="sortKey"
@@ -815,12 +857,17 @@ export default {
</issuable-list>
<template v-else-if="isSignedIn">
- <gl-empty-state
- :description="$options.i18n.noIssuesSignedInDescription"
- :title="$options.i18n.noIssuesSignedInTitle"
- :svg-path="emptyStateSvgPath"
- >
+ <gl-empty-state :title="$options.i18n.noIssuesSignedInTitle" :svg-path="emptyStateSvgPath">
+ <template #description>
+ <p>{{ $options.i18n.noIssuesSignedInDescription }}</p>
+ <p v-if="canCreateProjects">
+ <strong>{{ $options.i18n.noGroupIssuesSignedInDescription }}</strong>
+ </p>
+ </template>
<template #actions>
+ <gl-button v-if="canCreateProjects" :href="newProjectPath" variant="confirm">
+ {{ $options.i18n.newProjectLabel }}
+ </gl-button>
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ $options.i18n.newIssueLabel }}
</gl-button>
@@ -830,7 +877,7 @@ export default {
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
- <new-issue-dropdown v-if="showNewIssueDropdown" />
+ <new-issue-dropdown v-if="showNewIssueDropdown" class="gl-align-self-center" />
</template>
</gl-empty-state>
<hr />
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 0795df10a7c..74f801f685c 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -29,7 +29,11 @@ export const i18n = {
jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'),
jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'),
newIssueLabel: __('New issue'),
+ newProjectLabel: __('New project'),
noClosedIssuesTitle: __('There are no closed issues'),
+ noGroupIssuesSignedInDescription: __(
+ 'Issues exist in projects, so to create an issue, first create a project.',
+ ),
noOpenIssuesDescription: __('To keep this project going, create a new issue'),
noOpenIssuesTitle: __('There are no open issues'),
noIssuesSignedInDescription: __(
@@ -57,6 +61,8 @@ export const MAX_LIST_SIZE = 10;
export const PAGE_SIZE = 20;
export const PAGE_SIZE_MANUAL = 100;
export const PARAM_ASSIGNEE_ID = 'assignee_id';
+export const PARAM_FIRST_PAGE_SIZE = 'first_page_size';
+export const PARAM_LAST_PAGE_SIZE = 'last_page_size';
export const PARAM_PAGE_AFTER = 'page_after';
export const PARAM_PAGE_BEFORE = 'page_before';
export const PARAM_SORT = 'sort';
diff --git a/app/assets/javascripts/issues/list/index.js b/app/assets/javascripts/issues/list/index.js
index f5cb160e344..93333c31b34 100644
--- a/app/assets/javascripts/issues/list/index.js
+++ b/app/assets/javascripts/issues/list/index.js
@@ -80,8 +80,11 @@ export function mountIssuesListApp() {
autocompleteAwardEmojisPath,
calendarPath,
canBulkUpdate,
+ canCreateProjects,
canEdit,
canImportIssues,
+ canReadCrmContact,
+ canReadCrmOrganization,
email,
emailsHelpPagePath,
emptyStateSvgPath,
@@ -95,6 +98,7 @@ export function mountIssuesListApp() {
hasIssueWeightsFeature,
hasIterationsFeature,
hasMultipleIssueAssigneesFeature,
+ hasScopedLabelsFeature,
importCsvIssuesPath,
initialEmail,
initialSort,
@@ -107,6 +111,7 @@ export function mountIssuesListApp() {
markdownHelpPath,
maxAttachmentSize,
newIssuePath,
+ newProjectPath,
projectImportJiraPath,
quickActionsHelpPath,
releasesPath,
@@ -131,6 +136,9 @@ export function mountIssuesListApp() {
autocompleteAwardEmojisPath,
calendarPath,
canBulkUpdate: parseBoolean(canBulkUpdate),
+ canCreateProjects: parseBoolean(canCreateProjects),
+ canReadCrmContact: parseBoolean(canReadCrmContact),
+ canReadCrmOrganization: parseBoolean(canReadCrmOrganization),
emptyStateSvgPath,
fullPath,
groupPath,
@@ -141,6 +149,7 @@ export function mountIssuesListApp() {
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
hasIterationsFeature: parseBoolean(hasIterationsFeature),
hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature),
+ hasScopedLabelsFeature: parseBoolean(hasScopedLabelsFeature),
initialSort,
isAnonymousSearchDisabled: parseBoolean(isAnonymousSearchDisabled),
isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled),
@@ -149,6 +158,7 @@ export function mountIssuesListApp() {
isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath,
newIssuePath,
+ newProjectPath,
releasesPath,
rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink),
diff --git a/app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql
deleted file mode 100644
index ab91aab1218..00000000000
--- a/app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql
+++ /dev/null
@@ -1,136 +0,0 @@
-query getIssuesCountWithoutCrm(
- $isProject: Boolean = false
- $fullPath: ID!
- $iid: String
- $search: String
- $assigneeId: String
- $assigneeUsernames: [String!]
- $authorUsername: String
- $confidential: Boolean
- $labelName: [String]
- $milestoneTitle: [String]
- $milestoneWildcardId: MilestoneWildcardId
- $myReactionEmoji: String
- $releaseTag: [String!]
- $releaseTagWildcardId: ReleaseTagWildcardId
- $types: [IssueType!]
- $not: NegatedIssueFilterInput
-) {
- group(fullPath: $fullPath) @skip(if: $isProject) {
- id
- openedIssues: issues(
- includeSubgroups: true
- state: opened
- iid: $iid
- search: $search
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- types: $types
- not: $not
- ) {
- count
- }
- closedIssues: issues(
- includeSubgroups: true
- state: closed
- iid: $iid
- search: $search
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- types: $types
- not: $not
- ) {
- count
- }
- allIssues: issues(
- includeSubgroups: true
- state: all
- iid: $iid
- search: $search
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- types: $types
- not: $not
- ) {
- count
- }
- }
- project(fullPath: $fullPath) @include(if: $isProject) {
- id
- openedIssues: issues(
- state: opened
- iid: $iid
- search: $search
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- releaseTag: $releaseTag
- releaseTagWildcardId: $releaseTagWildcardId
- types: $types
- not: $not
- ) {
- count
- }
- closedIssues: issues(
- state: closed
- iid: $iid
- search: $search
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- releaseTag: $releaseTag
- releaseTagWildcardId: $releaseTagWildcardId
- types: $types
- not: $not
- ) {
- count
- }
- allIssues: issues(
- state: all
- iid: $iid
- search: $search
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- releaseTag: $releaseTag
- releaseTagWildcardId: $releaseTagWildcardId
- types: $types
- not: $not
- ) {
- count
- }
- }
-}
diff --git a/app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql
deleted file mode 100644
index 4a8b1dfd618..00000000000
--- a/app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql
+++ /dev/null
@@ -1,94 +0,0 @@
-#import "~/graphql_shared/fragments/page_info.fragment.graphql"
-#import "./issue.fragment.graphql"
-
-query getIssuesWithoutCrm(
- $hideUsers: Boolean = false
- $isProject: Boolean = false
- $isSignedIn: Boolean = false
- $fullPath: ID!
- $iid: String
- $search: String
- $sort: IssueSort
- $state: IssuableState
- $assigneeId: String
- $assigneeUsernames: [String!]
- $authorUsername: String
- $confidential: Boolean
- $labelName: [String]
- $milestoneTitle: [String]
- $milestoneWildcardId: MilestoneWildcardId
- $myReactionEmoji: String
- $releaseTag: [String!]
- $releaseTagWildcardId: ReleaseTagWildcardId
- $types: [IssueType!]
- $not: NegatedIssueFilterInput
- $beforeCursor: String
- $afterCursor: String
- $firstPageSize: Int
- $lastPageSize: Int
-) {
- group(fullPath: $fullPath) @skip(if: $isProject) {
- id
- issues(
- includeSubgroups: true
- iid: $iid
- search: $search
- sort: $sort
- state: $state
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- types: $types
- not: $not
- before: $beforeCursor
- after: $afterCursor
- first: $firstPageSize
- last: $lastPageSize
- ) {
- pageInfo {
- ...PageInfo
- }
- nodes {
- ...IssueFragment
- reference(full: true)
- }
- }
- }
- project(fullPath: $fullPath) @include(if: $isProject) {
- id
- issues(
- iid: $iid
- search: $search
- sort: $sort
- state: $state
- assigneeId: $assigneeId
- assigneeUsernames: $assigneeUsernames
- authorUsername: $authorUsername
- confidential: $confidential
- labelName: $labelName
- milestoneTitle: $milestoneTitle
- milestoneWildcardId: $milestoneWildcardId
- myReactionEmoji: $myReactionEmoji
- releaseTag: $releaseTag
- releaseTagWildcardId: $releaseTagWildcardId
- types: $types
- not: $not
- before: $beforeCursor
- after: $afterCursor
- first: $firstPageSize
- last: $lastPageSize
- ) {
- pageInfo {
- ...PageInfo
- }
- nodes {
- ...IssueFragment
- }
- }
- }
-}
diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js
index 3ca93069628..dfdc6e27f0d 100644
--- a/app/assets/javascripts/issues/list/utils.js
+++ b/app/assets/javascripts/issues/list/utils.js
@@ -46,8 +46,15 @@ import {
WEIGHT_DESC,
} from './constants';
-export const getInitialPageParams = (sortKey, afterCursor, beforeCursor) => ({
- firstPageSize: sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE,
+export const getInitialPageParams = (
+ sortKey,
+ firstPageSize = sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE,
+ lastPageSize,
+ afterCursor,
+ beforeCursor,
+) => ({
+ firstPageSize: lastPageSize ? undefined : firstPageSize,
+ lastPageSize,
afterCursor,
beforeCursor,
});