Commit 5ccc5eb3 authored by Jacques Erasmus's avatar Jacques Erasmus

Merge branch '322755-simplify-issue-counts-query' into 'master'

Simplify issues counts query in issues page refactor

See merge request gitlab-org/gitlab!69310
parents 3bb6a56a 193188b0
......@@ -9,8 +9,8 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { cloneDeep } from 'lodash';
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 createFlash from '~/flash';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
......@@ -21,7 +21,6 @@ import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import {
CREATED_DESC,
i18n,
issuesCountSmartQueryBase,
MAX_LIST_SIZE,
PAGE_SIZE,
PARAM_DUE_DATE,
......@@ -164,18 +163,16 @@ export default {
},
},
data() {
const filterTokens = getFilterTokens(window.location.search);
const state = getParameterByName(PARAM_STATE);
const sortKey = getSortKey(getParameterByName(PARAM_SORT));
const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC;
this.initialFilterTokens = cloneDeep(filterTokens);
return {
dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
filterTokens,
filterTokens: getFilterTokens(window.location.search),
issues: [],
issuesCounts: {},
pageInfo: {},
pageParams: getInitialPageParams(sortKey),
showBulkEditSidebar: false,
......@@ -202,40 +199,21 @@ export default {
},
debounce: 200,
},
countOpened: {
...issuesCountSmartQueryBase,
issuesCounts: {
query: getIssuesCountsQuery,
variables() {
return {
...this.queryVariables,
state: IssuableStates.Opened,
};
},
skip() {
return !this.hasAnyIssues;
return this.queryVariables;
},
},
countClosed: {
...issuesCountSmartQueryBase,
variables() {
return {
...this.queryVariables,
state: IssuableStates.Closed,
};
update: ({ project }) => project ?? {},
error(error) {
createFlash({ message: this.$options.i18n.errorFetchingCounts, captureError: true, error });
},
skip() {
return !this.hasAnyIssues;
},
},
countAll: {
...issuesCountSmartQueryBase,
variables() {
return {
...this.queryVariables,
state: IssuableStates.All,
};
},
skip() {
return !this.hasAnyIssues;
debounce: 200,
context: {
isSingleRequest: true,
},
},
},
......@@ -263,6 +241,9 @@ export default {
isOpenTab() {
return this.state === IssuableStates.Opened;
},
showCsvButtons() {
return this.isSignedIn;
},
apiFilterParams() {
return convertToApiParams(this.filterTokens);
},
......@@ -405,10 +386,11 @@ export default {
return getSortOptions(this.hasIssueWeightsFeature, this.hasBlockedIssuesFeature);
},
tabCounts() {
const { openedIssues, closedIssues, allIssues } = this.issuesCounts;
return {
[IssuableStates.Opened]: this.countOpened,
[IssuableStates.Closed]: this.countClosed,
[IssuableStates.All]: this.countAll,
[IssuableStates.Opened]: openedIssues?.count,
[IssuableStates.Closed]: closedIssues?.count,
[IssuableStates.All]: allIssues?.count,
};
},
currentTabCount() {
......@@ -584,13 +566,13 @@ export default {
})
.then(() => {
const serializedVariables = JSON.stringify(this.queryVariables);
this.$apollo.mutate({
return this.$apollo.mutate({
mutation: reorderIssuesMutation,
variables: { oldIndex, newIndex, serializedVariables },
});
})
.catch(() => {
createFlash({ message: this.$options.i18n.reorderError });
.catch((error) => {
createFlash({ message: this.$options.i18n.reorderError, captureError: true, error });
});
},
handleSort(sortKey) {
......@@ -613,7 +595,7 @@ export default {
recent-searches-storage-key="issues"
:search-input-placeholder="$options.i18n.searchPlaceholder"
:search-tokens="searchTokens"
:initial-filter-value="initialFilterTokens"
:initial-filter-value="filterTokens"
:sort-options="sortOptions"
:initial-sort-by="sortKey"
:issuables="issues"
......@@ -653,7 +635,7 @@ export default {
:aria-label="$options.i18n.calendarLabel"
/>
<csv-import-export-buttons
v-if="isSignedIn"
v-if="showCsvButtons"
class="gl-md-mr-3"
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
......@@ -766,6 +748,7 @@ export default {
{{ $options.i18n.newIssueLabel }}
</gl-button>
<csv-import-export-buttons
v-if="showCsvButtons"
class="gl-mr-3"
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
......
import getIssuesCountQuery from 'ee_else_ce/issues_list/queries/get_issues_count.query.graphql';
import createFlash from '~/flash';
import { __, s__ } from '~/locale';
import {
FILTER_ANY,
......@@ -351,15 +349,3 @@ export const filters = {
},
},
};
export const issuesCountSmartQueryBase = {
query: getIssuesCountQuery,
context: {
isSingleRequest: true,
},
update: ({ project }) => project?.issues.count,
error(error) {
createFlash({ message: i18n.errorFetchingCounts, captureError: true, error });
},
debounce: 200,
};
query getIssuesCount(
$fullPath: ID!
$search: String
$state: IssuableState
$assigneeId: String
$assigneeUsernames: [String!]
$authorUsername: String
......@@ -12,9 +11,37 @@ query getIssuesCount(
$not: NegatedIssueFilterInput
) {
project(fullPath: $fullPath) {
issues(
openedIssues: issues(
state: opened
search: $search
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
labelName: $labelName
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
types: $types
not: $not
) {
count
}
closedIssues: issues(
state: closed
search: $search
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
labelName: $labelName
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
types: $types
not: $not
) {
count
}
allIssues: issues(
state: all
search: $search
state: $state
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
......
query getIssuesCount(
$fullPath: ID!
$search: String
$state: IssuableState
$assigneeId: String
$assigneeUsernames: [String!]
$authorUsername: String
......@@ -16,9 +15,45 @@ query getIssuesCount(
$not: NegatedIssueFilterInput
) {
project(fullPath: $fullPath) {
issues(
openedIssues: issues(
state: opened
search: $search
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
labelName: $labelName
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
types: $types
epicId: $epicId
iterationId: $iterationId
iterationWildcardId: $iterationWildcardId
weight: $weight
not: $not
) {
count
}
closedIssues: issues(
state: closed
search: $search
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
labelName: $labelName
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
types: $types
epicId: $epicId
iterationId: $iterationId
iterationWildcardId: $iterationWildcardId
weight: $weight
not: $not
) {
count
}
allIssues: issues(
state: all
search: $search
state: $state
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
......
......@@ -5,17 +5,17 @@ import { cloneDeep } from 'lodash';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import getIssuesCountQuery from 'ee_else_ce/issues_list/queries/get_issues_count.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql';
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 {
getIssuesCountsQueryResponse,
getIssuesQueryResponse,
filteredTokens,
locationSearch,
urlParams,
getIssuesCountQueryResponse,
} from 'jest/issues_list/mock_data';
import createFlash from '~/flash';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
......@@ -97,12 +97,12 @@ describe('IssuesListApp component', () => {
const mountComponent = ({
provide = {},
issuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse),
issuesQueryCountResponse = jest.fn().mockResolvedValue(getIssuesCountQueryResponse),
issuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse),
mountFn = shallowMount,
} = {}) => {
const requestHandlers = [
[getIssuesQuery, issuesQueryResponse],
[getIssuesCountQuery, issuesQueryCountResponse],
[getIssuesCountsQuery, issuesCountsQueryResponse],
];
const apolloProvider = createMockApollo(requestHandlers);
......@@ -571,9 +571,9 @@ describe('IssuesListApp component', () => {
describe('errors', () => {
describe.each`
error | mountOption | message
${'fetching issues'} | ${'issuesQueryResponse'} | ${IssuesListApp.i18n.errorFetchingIssues}
${'fetching issue counts'} | ${'issuesQueryCountResponse'} | ${IssuesListApp.i18n.errorFetchingCounts}
error | mountOption | message
${'fetching issues'} | ${'issuesQueryResponse'} | ${IssuesListApp.i18n.errorFetchingIssues}
${'fetching issue counts'} | ${'issuesCountsQueryResponse'} | ${IssuesListApp.i18n.errorFetchingCounts}
`('when there is an error $error', ({ mountOption, message }) => {
beforeEach(() => {
wrapper = mountComponent({
......@@ -696,7 +696,11 @@ describe('IssuesListApp component', () => {
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: IssuesListApp.i18n.reorderError });
expect(createFlash).toHaveBeenCalledWith({
message: IssuesListApp.i18n.reorderError,
captureError: true,
error: new Error('Request failed with status code 500'),
});
});
});
});
......
......@@ -70,10 +70,16 @@ export const getIssuesQueryResponse = {
},
};
export const getIssuesCountQueryResponse = {
export const getIssuesCountsQueryResponse = {
data: {
project: {
issues: {
openedIssues: {
count: 1,
},
closedIssues: {
count: 1,
},
allIssues: {
count: 1,
},
},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment