diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue
index f9414076260..7635536c54f 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_item.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue
@@ -50,9 +50,6 @@ export default {
},
},
computed: {
- issuableId() {
- return getIdFromGraphQLId(this.issuable.id);
- },
createdInPastDay() {
const createdSecondsAgo = differenceInSeconds(new Date(this.issuable.createdAt), new Date());
return createdSecondsAgo < SECONDS_IN_DAY;
@@ -64,7 +61,7 @@ export default {
return this.issuable.gitlabWebUrl || this.issuable.webUrl;
},
authorId() {
- return getIdFromGraphQLId(this.author.id);
+ return getIdFromGraphQLId(`${this.author.id}`);
},
isIssuableUrlExternal() {
return isExternal(this.webUrl);
@@ -73,10 +70,10 @@ export default {
return this.issuable.labels?.nodes || this.issuable.labels || [];
},
labelIdsString() {
- return JSON.stringify(this.labels.map((label) => getIdFromGraphQLId(label.id)));
+ return JSON.stringify(this.labels.map((label) => label.id));
},
assignees() {
- return this.issuable.assignees?.nodes || this.issuable.assignees || [];
+ return this.issuable.assignees || [];
},
createdAt() {
return sprintf(__('created %{timeAgo}'), {
@@ -84,9 +81,6 @@ export default {
});
},
updatedAt() {
- if (!this.issuable.updatedAt) {
- return '';
- }
return sprintf(__('updated %{timeAgo}'), {
timeAgo: getTimeago().format(this.issuable.updatedAt),
});
@@ -163,7 +157,7 @@ export default {
{{ issuable.title }}
diff --git a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
index c13f80d2e31..45584205be0 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
@@ -2,7 +2,6 @@
import { GlSkeletonLoading, GlPagination } from '@gitlab/ui';
import { uniqueId } from 'lodash';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
@@ -212,7 +211,7 @@ export default {
},
methods: {
issuableId(issuable) {
- return getIdFromGraphQLId(issuable.id) || issuable.iid || uniqueId();
+ return issuable.id || issuable.iid || uniqueId();
},
issuableChecked(issuable) {
return this.checkedIssuables[this.issuableId(issuable)]?.checked;
diff --git a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue b/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
index 70d73aca925..8d00d337bac 100644
--- a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
+++ b/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
@@ -42,9 +42,6 @@ export default {
}
return __('Milestone');
},
- milestoneLink() {
- return this.issue.milestone.webPath || this.issue.milestone.webUrl;
- },
dueDate() {
return this.issue.dueDate && dateInWords(new Date(this.issue.dueDate), true);
},
@@ -52,7 +49,7 @@ export default {
return isInPast(new Date(this.issue.dueDate));
},
timeEstimate() {
- return this.issue.humanTimeEstimate || this.issue.timeStats?.humanTimeEstimate;
+ return this.issue.timeStats?.humanTimeEstimate;
},
showHealthStatus() {
return this.hasIssuableHealthStatusFeature && this.issue.healthStatus;
@@ -88,7 +85,7 @@ export default {
class="issuable-milestone gl-display-none gl-sm-display-inline-block! gl-mr-3"
data-testid="issuable-milestone"
>
-
+
{{ issue.milestone.title }}
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 e53ef5f5c4b..4ba2bc3a8e3 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -9,21 +9,24 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
-import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
+import { toNumber } from 'lodash';
import createFlash from '~/flash';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import {
+ API_PARAM,
+ apiSortParams,
CREATED_DESC,
i18n,
MAX_LIST_SIZE,
PAGE_SIZE,
PARAM_DUE_DATE,
+ PARAM_PAGE,
PARAM_SORT,
PARAM_STATE,
- RELATIVE_POSITION_ASC,
+ RELATIVE_POSITION_DESC,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL,
@@ -34,19 +37,19 @@ import {
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_WEIGHT,
UPDATED_DESC,
+ URL_PARAM,
urlSortParams,
} from '~/issues_list/constants';
import {
- convertToApiParams,
+ convertToParams,
convertToSearchQuery,
- convertToUrlParams,
getDueDateValue,
getFilterTokens,
getSortKey,
getSortOptions,
} from '~/issues_list/utils';
import axios from '~/lib/utils/axios_utils';
-import { getParameterByName } from '~/lib/utils/common_utils';
+import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/common_utils';
import {
DEFAULT_NONE_ANY,
OPERATOR_IS_ONLY,
@@ -104,6 +107,9 @@ export default {
emptyStateSvgPath: {
default: '',
},
+ endpoint: {
+ default: '',
+ },
exportCsvPath: {
default: '',
},
@@ -167,53 +173,15 @@ export default {
dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
filterTokens: getFilterTokens(window.location.search),
+ isLoading: false,
issues: [],
- page: 1,
- pageInfo: {},
- pageParams: {
- firstPageSize: PAGE_SIZE,
- },
+ page: toNumber(getParameterByName(PARAM_PAGE)) || 1,
showBulkEditSidebar: false,
sortKey: getSortKey(getParameterByName(PARAM_SORT)) || defaultSortKey,
state: state || IssuableStates.Opened,
totalIssues: 0,
};
},
- apollo: {
- issues: {
- query: getIssuesQuery,
- variables() {
- const filterParams = {
- ...this.apiFilterParams,
- };
-
- if (filterParams.epicId) {
- filterParams.epicId = filterParams.epicId.split('::&').pop();
- } else if (filterParams.not?.epicId) {
- filterParams.not.epicId = filterParams.not.epicId.split('::&').pop();
- }
-
- return {
- projectPath: this.projectPath,
- search: this.searchQuery,
- sort: this.sortKey,
- state: this.state,
- ...this.pageParams,
- ...filterParams,
- };
- },
- update: ({ project }) => project.issues.nodes,
- result({ data }) {
- this.pageInfo = data.project.issues.pageInfo;
- this.totalIssues = data.project.issues.count;
- this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
- },
- error() {
- createFlash({ message: this.$options.i18n.errorFetchingIssues });
- },
- debounce: 200,
- },
- },
computed: {
hasSearch() {
return this.searchQuery || Object.keys(this.urlFilterParams).length;
@@ -222,22 +190,16 @@ export default {
return this.showBulkEditSidebar || !this.issues.length;
},
isManualOrdering() {
- return this.sortKey === RELATIVE_POSITION_ASC;
+ return this.sortKey === RELATIVE_POSITION_DESC;
},
isOpenTab() {
return this.state === IssuableStates.Opened;
},
- nextPage() {
- return Number(this.pageInfo.hasNextPage);
- },
- previousPage() {
- return Number(this.pageInfo.hasPreviousPage);
- },
apiFilterParams() {
- return convertToApiParams(this.filterTokens);
+ return convertToParams(this.filterTokens, API_PARAM);
},
urlFilterParams() {
- return convertToUrlParams(this.filterTokens);
+ return convertToParams(this.filterTokens, URL_PARAM);
},
searchQuery() {
return convertToSearchQuery(this.filterTokens) || undefined;
@@ -252,7 +214,6 @@ export default {
dataType: 'user',
unique: true,
defaultAuthors: [],
- operators: OPERATOR_IS_ONLY,
fetchAuthors: this.fetchUsers,
},
{
@@ -279,7 +240,7 @@ export default {
title: TOKEN_TITLE_LABEL,
icon: 'labels',
token: LabelToken,
- defaultLabels: DEFAULT_NONE_ANY,
+ defaultLabels: [],
fetchLabels: this.fetchLabels,
},
];
@@ -372,9 +333,10 @@ export default {
return {
due_date: this.dueDateFilter,
+ page: this.page,
search: this.searchQuery,
- sort: urlSortParams[this.sortKey],
state: this.state,
+ ...urlSortParams[this.sortKey],
...filterParams,
};
},
@@ -384,6 +346,7 @@ export default {
},
mounted() {
eventHub.$on('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
+ this.fetchIssues();
},
beforeDestroy() {
eventHub.$off('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
@@ -423,19 +386,59 @@ export default {
return this.fetchWithCache(this.projectMilestonesPath, 'milestones', 'title', search, true);
},
fetchIterations(search) {
- const number = Number(search);
- return !search || Number.isNaN(number)
- ? axios.get(this.projectIterationsPath, { params: { search } })
- : axios.get(this.projectIterationsPath, { params: { id: number } });
+ return axios.get(this.projectIterationsPath, { params: { search } });
},
fetchUsers(search) {
return axios.get(this.autocompleteUsersPath, { params: { search } });
},
+ fetchIssues() {
+ if (!this.hasProjectIssues) {
+ return undefined;
+ }
+
+ this.isLoading = true;
+
+ const filterParams = {
+ ...this.apiFilterParams,
+ };
+
+ if (filterParams.epic_id) {
+ filterParams.epic_id = filterParams.epic_id.split('::&').pop();
+ } else if (filterParams['not[epic_id]']) {
+ filterParams['not[epic_id]'] = filterParams['not[epic_id]'].split('::&').pop();
+ }
+
+ return axios
+ .get(this.endpoint, {
+ params: {
+ due_date: this.dueDateFilter,
+ page: this.page,
+ per_page: PAGE_SIZE,
+ search: this.searchQuery,
+ state: this.state,
+ with_labels_details: true,
+ ...apiSortParams[this.sortKey],
+ ...filterParams,
+ },
+ })
+ .then(({ data, headers }) => {
+ this.page = Number(headers['x-page']);
+ this.totalIssues = Number(headers['x-total']);
+ this.issues = data.map((issue) => convertObjectPropsToCamelCase(issue, { deep: true }));
+ this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
+ })
+ .catch(() => {
+ createFlash({ message: this.$options.i18n.errorFetchingIssues });
+ })
+ .finally(() => {
+ this.isLoading = false;
+ });
+ },
getExportCsvPathWithQuery() {
return `${this.exportCsvPath}${window.location.search}`;
},
getStatus(issue) {
- if (issue.closedAt && issue.moved) {
+ if (issue.closedAt && issue.movedToId) {
return this.$options.i18n.closedMoved;
}
if (issue.closedAt) {
@@ -466,30 +469,18 @@ export default {
},
handleClickTab(state) {
if (this.state !== state) {
- this.pageParams = {
- firstPageSize: PAGE_SIZE,
- };
this.page = 1;
}
this.state = state;
+ this.fetchIssues();
},
handleFilter(filter) {
this.filterTokens = filter;
+ this.fetchIssues();
},
handlePageChange(page) {
- if (page > this.page) {
- this.pageParams = {
- afterCursor: this.pageInfo.endCursor,
- firstPageSize: PAGE_SIZE,
- };
- } else {
- this.pageParams = {
- beforeCursor: this.pageInfo.startCursor,
- lastPageSize: PAGE_SIZE,
- };
- }
-
this.page = page;
+ this.fetchIssues();
},
handleReorder({ newIndex, oldIndex }) {
const issueToMove = this.issues[oldIndex];
@@ -526,6 +517,7 @@ export default {
},
handleSort(value) {
this.sortKey = value;
+ this.fetchIssues();
},
toggleBulkEditSidebar(showBulkEditSidebar) {
this.showBulkEditSidebar = showBulkEditSidebar;
@@ -549,13 +541,14 @@ export default {
:tabs="$options.IssuableListTabs"
:current-tab="state"
:tab-counts="tabCounts"
- :issuables-loading="$apollo.loading"
+ :issuables-loading="isLoading"
:is-manual-ordering="isManualOrdering"
:show-bulk-edit-sidebar="showBulkEditSidebar"
:show-pagination-controls="showPaginationControls"
+ :total-items="totalIssues"
:current-page="page"
- :previous-page="previousPage"
- :next-page="nextPage"
+ :previous-page="page - 1"
+ :next-page="page + 1"
:url-params="urlParams"
@click-tab="handleClickTab"
@filter="handleFilter"
@@ -638,7 +631,7 @@ export default {
diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js
index 414e7b756ef..06e140d6420 100644
--- a/app/assets/javascripts/issues_list/constants.js
+++ b/app/assets/javascripts/issues_list/constants.js
@@ -101,6 +101,7 @@ export const i18n = {
export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map';
export const PARAM_DUE_DATE = 'due_date';
+export const PARAM_PAGE = 'page';
export const PARAM_SORT = 'sort';
export const PARAM_STATE = 'state';
@@ -124,21 +125,21 @@ export const CREATED_ASC = 'CREATED_ASC';
export const CREATED_DESC = 'CREATED_DESC';
export const DUE_DATE_ASC = 'DUE_DATE_ASC';
export const DUE_DATE_DESC = 'DUE_DATE_DESC';
-export const LABEL_PRIORITY_ASC = 'LABEL_PRIORITY_ASC';
export const LABEL_PRIORITY_DESC = 'LABEL_PRIORITY_DESC';
export const MILESTONE_DUE_ASC = 'MILESTONE_DUE_ASC';
export const MILESTONE_DUE_DESC = 'MILESTONE_DUE_DESC';
export const POPULARITY_ASC = 'POPULARITY_ASC';
export const POPULARITY_DESC = 'POPULARITY_DESC';
-export const PRIORITY_ASC = 'PRIORITY_ASC';
export const PRIORITY_DESC = 'PRIORITY_DESC';
-export const RELATIVE_POSITION_ASC = 'RELATIVE_POSITION_ASC';
+export const RELATIVE_POSITION_DESC = 'RELATIVE_POSITION_DESC';
export const UPDATED_ASC = 'UPDATED_ASC';
export const UPDATED_DESC = 'UPDATED_DESC';
export const WEIGHT_ASC = 'WEIGHT_ASC';
export const WEIGHT_DESC = 'WEIGHT_DESC';
-const PRIORITY_ASC_SORT = 'priority_asc';
+const SORT_ASC = 'asc';
+const SORT_DESC = 'desc';
+
const CREATED_DATE_SORT = 'created_date';
const CREATED_ASC_SORT = 'created_asc';
const UPDATED_DESC_SORT = 'updated_desc';
@@ -146,30 +147,129 @@ const UPDATED_ASC_SORT = 'updated_asc';
const MILESTONE_SORT = 'milestone';
const MILESTONE_DUE_DESC_SORT = 'milestone_due_desc';
const DUE_DATE_DESC_SORT = 'due_date_desc';
-const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc';
const POPULARITY_ASC_SORT = 'popularity_asc';
const WEIGHT_DESC_SORT = 'weight_desc';
const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc';
+const BLOCKING_ISSUES = 'blocking_issues';
+
+export const apiSortParams = {
+ [PRIORITY_DESC]: {
+ order_by: PRIORITY,
+ sort: SORT_DESC,
+ },
+ [CREATED_ASC]: {
+ order_by: CREATED_AT,
+ sort: SORT_ASC,
+ },
+ [CREATED_DESC]: {
+ order_by: CREATED_AT,
+ sort: SORT_DESC,
+ },
+ [UPDATED_ASC]: {
+ order_by: UPDATED_AT,
+ sort: SORT_ASC,
+ },
+ [UPDATED_DESC]: {
+ order_by: UPDATED_AT,
+ sort: SORT_DESC,
+ },
+ [MILESTONE_DUE_ASC]: {
+ order_by: MILESTONE_DUE,
+ sort: SORT_ASC,
+ },
+ [MILESTONE_DUE_DESC]: {
+ order_by: MILESTONE_DUE,
+ sort: SORT_DESC,
+ },
+ [DUE_DATE_ASC]: {
+ order_by: DUE_DATE,
+ sort: SORT_ASC,
+ },
+ [DUE_DATE_DESC]: {
+ order_by: DUE_DATE,
+ sort: SORT_DESC,
+ },
+ [POPULARITY_ASC]: {
+ order_by: POPULARITY,
+ sort: SORT_ASC,
+ },
+ [POPULARITY_DESC]: {
+ order_by: POPULARITY,
+ sort: SORT_DESC,
+ },
+ [LABEL_PRIORITY_DESC]: {
+ order_by: LABEL_PRIORITY,
+ sort: SORT_DESC,
+ },
+ [RELATIVE_POSITION_DESC]: {
+ order_by: RELATIVE_POSITION,
+ per_page: 100,
+ sort: SORT_ASC,
+ },
+ [WEIGHT_ASC]: {
+ order_by: WEIGHT,
+ sort: SORT_ASC,
+ },
+ [WEIGHT_DESC]: {
+ order_by: WEIGHT,
+ sort: SORT_DESC,
+ },
+ [BLOCKING_ISSUES_DESC]: {
+ order_by: BLOCKING_ISSUES,
+ sort: SORT_DESC,
+ },
+};
export const urlSortParams = {
- [PRIORITY_ASC]: PRIORITY_ASC_SORT,
- [PRIORITY_DESC]: PRIORITY,
- [CREATED_ASC]: CREATED_ASC_SORT,
- [CREATED_DESC]: CREATED_DATE_SORT,
- [UPDATED_ASC]: UPDATED_ASC_SORT,
- [UPDATED_DESC]: UPDATED_DESC_SORT,
- [MILESTONE_DUE_ASC]: MILESTONE_SORT,
- [MILESTONE_DUE_DESC]: MILESTONE_DUE_DESC_SORT,
- [DUE_DATE_ASC]: DUE_DATE,
- [DUE_DATE_DESC]: DUE_DATE_DESC_SORT,
- [POPULARITY_ASC]: POPULARITY_ASC_SORT,
- [POPULARITY_DESC]: POPULARITY,
- [LABEL_PRIORITY_ASC]: LABEL_PRIORITY_ASC_SORT,
- [LABEL_PRIORITY_DESC]: LABEL_PRIORITY,
- [RELATIVE_POSITION_ASC]: RELATIVE_POSITION,
- [WEIGHT_ASC]: WEIGHT,
- [WEIGHT_DESC]: WEIGHT_DESC_SORT,
- [BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT,
+ [PRIORITY_DESC]: {
+ sort: PRIORITY,
+ },
+ [CREATED_ASC]: {
+ sort: CREATED_ASC_SORT,
+ },
+ [CREATED_DESC]: {
+ sort: CREATED_DATE_SORT,
+ },
+ [UPDATED_ASC]: {
+ sort: UPDATED_ASC_SORT,
+ },
+ [UPDATED_DESC]: {
+ sort: UPDATED_DESC_SORT,
+ },
+ [MILESTONE_DUE_ASC]: {
+ sort: MILESTONE_SORT,
+ },
+ [MILESTONE_DUE_DESC]: {
+ sort: MILESTONE_DUE_DESC_SORT,
+ },
+ [DUE_DATE_ASC]: {
+ sort: DUE_DATE,
+ },
+ [DUE_DATE_DESC]: {
+ sort: DUE_DATE_DESC_SORT,
+ },
+ [POPULARITY_ASC]: {
+ sort: POPULARITY_ASC_SORT,
+ },
+ [POPULARITY_DESC]: {
+ sort: POPULARITY,
+ },
+ [LABEL_PRIORITY_DESC]: {
+ sort: LABEL_PRIORITY,
+ },
+ [RELATIVE_POSITION_DESC]: {
+ sort: RELATIVE_POSITION,
+ per_page: 100,
+ },
+ [WEIGHT_ASC]: {
+ sort: WEIGHT,
+ },
+ [WEIGHT_DESC]: {
+ sort: WEIGHT_DESC_SORT,
+ },
+ [BLOCKING_ISSUES_DESC]: {
+ sort: BLOCKING_ISSUES_DESC_SORT,
+ },
};
export const MAX_LIST_SIZE = 10;
@@ -194,7 +294,12 @@ export const TOKEN_TYPE_WEIGHT = 'weight';
export const filters = {
[TOKEN_TYPE_AUTHOR]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'authorUsername',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'author_username',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[author_username]',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -207,8 +312,13 @@ export const filters = {
},
[TOKEN_TYPE_ASSIGNEE]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'assigneeUsernames',
- [SPECIAL_FILTER]: 'assigneeId',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'assignee_username',
+ [SPECIAL_FILTER]: 'assignee_id',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[assignee_username]',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -223,7 +333,12 @@ export const filters = {
},
[TOKEN_TYPE_MILESTONE]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'milestoneTitle',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'milestone',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[milestone]',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -236,13 +351,16 @@ export const filters = {
},
[TOKEN_TYPE_LABEL]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'labelName',
- [SPECIAL_FILTER]: 'labelName',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'labels',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[labels]',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'label_name[]',
- [SPECIAL_FILTER]: 'label_name[]',
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[label_name][]',
@@ -251,8 +369,10 @@ export const filters = {
},
[TOKEN_TYPE_MY_REACTION]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'myReactionEmoji',
- [SPECIAL_FILTER]: 'myReactionEmoji',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'my_reaction_emoji',
+ [SPECIAL_FILTER]: 'my_reaction_emoji',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -263,7 +383,9 @@ export const filters = {
},
[TOKEN_TYPE_CONFIDENTIAL]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'confidential',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'confidential',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -273,23 +395,33 @@ export const filters = {
},
[TOKEN_TYPE_ITERATION]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'iterationId',
- [SPECIAL_FILTER]: 'iterationWildcardId',
- },
- [URL_PARAM]: {
[OPERATOR_IS]: {
- [NORMAL_FILTER]: 'iteration_id',
+ [NORMAL_FILTER]: 'iteration_title',
[SPECIAL_FILTER]: 'iteration_id',
},
[OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[iteration_id]',
+ [NORMAL_FILTER]: 'not[iteration_title]',
+ },
+ },
+ [URL_PARAM]: {
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'iteration_title',
+ [SPECIAL_FILTER]: 'iteration_id',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[iteration_title]',
},
},
},
[TOKEN_TYPE_EPIC]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'epicId',
- [SPECIAL_FILTER]: 'epicId',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'epic_id',
+ [SPECIAL_FILTER]: 'epic_id',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[epic_id]',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -303,8 +435,13 @@ export const filters = {
},
[TOKEN_TYPE_WEIGHT]: {
[API_PARAM]: {
- [NORMAL_FILTER]: 'weight',
- [SPECIAL_FILTER]: 'weight',
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'weight',
+ [SPECIAL_FILTER]: 'weight',
+ },
+ [OPERATOR_IS_NOT]: {
+ [NORMAL_FILTER]: 'not[weight]',
+ },
},
[URL_PARAM]: {
[OPERATOR_IS]: {
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js
index 7d800caa33d..d0c9462a3d7 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues_list/index.js
@@ -73,13 +73,6 @@ export function mountIssuesListApp() {
return false;
}
- Vue.use(VueApollo);
-
- const defaultClient = createDefaultClient({}, { assumeImmutableResults: true });
- const apolloProvider = new VueApollo({
- defaultClient,
- });
-
const {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
@@ -90,6 +83,7 @@ export function mountIssuesListApp() {
email,
emailsHelpPagePath,
emptyStateSvgPath,
+ endpoint,
exportCsvPath,
groupEpicsPath,
hasBlockedIssuesFeature,
@@ -121,13 +115,14 @@ export function mountIssuesListApp() {
el,
// Currently does not use Vue Apollo, but need to provide {} for now until the
// issue is fixed upstream in https://github.com/vuejs/vue-apollo/pull/1153
- apolloProvider,
+ apolloProvider: {},
provide: {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
calendarPath,
canBulkUpdate: parseBoolean(canBulkUpdate),
emptyStateSvgPath,
+ endpoint,
groupEpicsPath,
hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
diff --git a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql b/app/assets/javascripts/issues_list/queries/get_issues.query.graphql
deleted file mode 100644
index ded70d2d7ba..00000000000
--- a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql
+++ /dev/null
@@ -1,45 +0,0 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
-#import "./issue_info.fragment.graphql"
-
-query getProjectIssues(
- $projectPath: ID!
- $search: String
- $sort: IssueSort
- $state: IssuableState
- $assigneeId: String
- $authorUsername: String
- $assigneeUsernames: [String!]
- $milestoneTitle: [String]
- $labelName: [String]
- $not: NegatedIssueFilterInput
- $beforeCursor: String
- $afterCursor: String
- $firstPageSize: Int
- $lastPageSize: Int
-) {
- project(fullPath: $projectPath) {
- issues(
- search: $search
- sort: $sort
- state: $state
- assigneeId: $assigneeId
- authorUsername: $authorUsername
- assigneeUsernames: $assigneeUsernames
- milestoneTitle: $milestoneTitle
- labelName: $labelName
- not: $not
- before: $beforeCursor
- after: $afterCursor
- first: $firstPageSize
- last: $lastPageSize
- ) {
- count
- pageInfo {
- ...PageInfo
- }
- nodes {
- ...IssueInfo
- }
- }
- }
-}
diff --git a/app/assets/javascripts/issues_list/queries/issue_info.fragment.graphql b/app/assets/javascripts/issues_list/queries/issue_info.fragment.graphql
deleted file mode 100644
index c219dc8e35b..00000000000
--- a/app/assets/javascripts/issues_list/queries/issue_info.fragment.graphql
+++ /dev/null
@@ -1,51 +0,0 @@
-fragment IssueInfo on Issue {
- id
- iid
- closedAt
- confidential
- createdAt
- downvotes
- dueDate
- humanTimeEstimate
- moved
- title
- updatedAt
- upvotes
- userDiscussionsCount
- webUrl
- assignees {
- nodes {
- id
- avatarUrl
- name
- username
- webUrl
- }
- }
- author {
- id
- avatarUrl
- name
- username
- webUrl
- }
- labels {
- nodes {
- id
- color
- title
- description
- }
- }
- milestone {
- id
- dueDate
- startDate
- webPath
- title
- }
- taskCompletionStatus {
- completedCount
- count
- }
-}
diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js
index 23e5b70b353..b5ec44198da 100644
--- a/app/assets/javascripts/issues_list/utils.js
+++ b/app/assets/javascripts/issues_list/utils.js
@@ -1,5 +1,4 @@
import {
- API_PARAM,
BLOCKING_ISSUES_DESC,
CREATED_ASC,
CREATED_DESC,
@@ -7,36 +6,29 @@ import {
DUE_DATE_DESC,
DUE_DATE_VALUES,
filters,
- LABEL_PRIORITY_ASC,
LABEL_PRIORITY_DESC,
MILESTONE_DUE_ASC,
MILESTONE_DUE_DESC,
NORMAL_FILTER,
POPULARITY_ASC,
POPULARITY_DESC,
- PRIORITY_ASC,
PRIORITY_DESC,
- RELATIVE_POSITION_ASC,
+ RELATIVE_POSITION_DESC,
SPECIAL_FILTER,
SPECIAL_FILTER_VALUES,
TOKEN_TYPE_ASSIGNEE,
- TOKEN_TYPE_ITERATION,
UPDATED_ASC,
UPDATED_DESC,
- URL_PARAM,
urlSortParams,
WEIGHT_ASC,
WEIGHT_DESC,
} from '~/issues_list/constants';
import { isPositiveInteger } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
-import {
- FILTERED_SEARCH_TERM,
- OPERATOR_IS_NOT,
-} from '~/vue_shared/components/filtered_search_bar/constants';
+import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
export const getSortKey = (sort) =>
- Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort);
+ Object.keys(urlSortParams).find((key) => urlSortParams[key].sort === sort);
export const getDueDateValue = (value) => (DUE_DATE_VALUES.includes(value) ? value : undefined);
@@ -46,7 +38,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: 1,
title: __('Priority'),
sortDirection: {
- ascending: PRIORITY_ASC,
+ ascending: PRIORITY_DESC,
descending: PRIORITY_DESC,
},
},
@@ -94,7 +86,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: 7,
title: __('Label priority'),
sortDirection: {
- ascending: LABEL_PRIORITY_ASC,
+ ascending: LABEL_PRIORITY_DESC,
descending: LABEL_PRIORITY_DESC,
},
},
@@ -102,8 +94,8 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: 8,
title: __('Manual'),
sortDirection: {
- ascending: RELATIVE_POSITION_ASC,
- descending: RELATIVE_POSITION_ASC,
+ ascending: RELATIVE_POSITION_DESC,
+ descending: RELATIVE_POSITION_DESC,
},
},
];
@@ -186,36 +178,12 @@ const getFilterType = (data, tokenType = '') =>
? SPECIAL_FILTER
: NORMAL_FILTER;
-const isIterationSpecialValue = (tokenType, value) =>
- tokenType === TOKEN_TYPE_ITERATION && SPECIAL_FILTER_VALUES.includes(value);
-
-export const convertToApiParams = (filterTokens) => {
- const params = {};
- const not = {};
-
- filterTokens
- .filter((token) => token.type !== FILTERED_SEARCH_TERM)
- .forEach((token) => {
- const filterType = getFilterType(token.value.data, token.type);
- const field = filters[token.type][API_PARAM][filterType];
- const obj = token.value.operator === OPERATOR_IS_NOT ? not : params;
- const data = isIterationSpecialValue(token.type, token.value.data)
- ? token.value.data.toUpperCase()
- : token.value.data;
- Object.assign(obj, {
- [field]: obj[field] ? [obj[field], data].flat() : data,
- });
- });
-
- return Object.keys(not).length ? Object.assign(params, { not }) : params;
-};
-
-export const convertToUrlParams = (filterTokens) =>
+export const convertToParams = (filterTokens, paramType) =>
filterTokens
.filter((token) => token.type !== FILTERED_SEARCH_TERM)
.reduce((acc, token) => {
const filterType = getFilterType(token.value.data, token.type);
- const param = filters[token.type][URL_PARAM][token.value.operator]?.[filterType];
+ const param = filters[token.type][paramType][token.value.operator]?.[filterType];
return Object.assign(acc, {
[param]: acc[param] ? [acc[param], token.value.data].flat() : token.value.data,
});
diff --git a/app/assets/javascripts/releases/components/app_index_apollo_client.vue b/app/assets/javascripts/releases/components/app_index_apollo_client.vue
index be4d7d8543c..b915ec9c98f 100644
--- a/app/assets/javascripts/releases/components/app_index_apollo_client.vue
+++ b/app/assets/javascripts/releases/components/app_index_apollo_client.vue
@@ -2,6 +2,7 @@
import { GlButton } from '@gitlab/ui';
import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/common_utils';
+import { scrollUp } from '~/lib/utils/scroll_utils';
import { __ } from '~/locale';
import { PAGE_SIZE } from '~/releases/constants';
import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
@@ -9,6 +10,7 @@ import { convertAllReleasesGraphQLResponse } from '~/releases/util';
import ReleaseBlock from './release_block.vue';
import ReleaseSkeletonLoader from './release_skeleton_loader.vue';
import ReleasesEmptyState from './releases_empty_state.vue';
+import ReleasesPaginationApolloClient from './releases_pagination_apollo_client.vue';
export default {
name: 'ReleasesIndexApolloClientApp',
@@ -17,6 +19,7 @@ export default {
ReleaseBlock,
ReleaseSkeletonLoader,
ReleasesEmptyState,
+ ReleasesPaginationApolloClient,
},
inject: {
projectPath: {
@@ -85,6 +88,16 @@ export default {
return convertAllReleasesGraphQLResponse(this.graphqlResponse).data;
},
+ pageInfo() {
+ if (!this.graphqlResponse || this.hasError) {
+ return {
+ hasPreviousPage: false,
+ hasNextPage: false,
+ };
+ }
+
+ return this.graphqlResponse.data.project.releases.pageInfo;
+ },
shouldRenderEmptyState() {
return !this.releases.length && !this.hasError && !this.isLoading;
},
@@ -94,6 +107,13 @@ export default {
shouldRenderLoadingIndicator() {
return this.isLoading && !this.hasError;
},
+ shouldRenderPagination() {
+ return (
+ !this.isLoading &&
+ !this.hasError &&
+ (this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage)
+ );
+ },
},
created() {
this.updateQueryParamsFromUrl();
@@ -108,6 +128,16 @@ export default {
this.cursors.before = getParameterByName('before');
this.cursors.after = getParameterByName('after');
},
+ onPaginationButtonPress() {
+ this.updateQueryParamsFromUrl();
+
+ // In some cases, Apollo Client is able to pull its results from the cache instead of making
+ // a new network request. In these cases, the page's content gets swapped out immediately without
+ // changing the page's scroll, leaving the user looking at the bottom of the new page.
+ // To make the experience consistent, regardless of how the data is sourced, we manually
+ // scroll to the top of the page every time a pagination button is pressed.
+ scrollUp();
+ },
},
i18n: {
newRelease: __('New release'),
@@ -140,6 +170,13 @@ export default {
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
/>
+
+