diff options
Diffstat (limited to 'app/assets/javascripts/issues/list')
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, }); |