diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index fae5cc1a74c..ac6af44a0dd 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-6e58da454633a53c86d14a59587ee7a6a9d11031
+58261cb08bae1bae4fed28173ce9457bd9728c76
diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue
index 7635536c54f..f9414076260 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_item.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue
@@ -50,6 +50,9 @@ 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;
@@ -61,7 +64,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);
@@ -70,10 +73,10 @@ export default {
return this.issuable.labels?.nodes || this.issuable.labels || [];
},
labelIdsString() {
- return JSON.stringify(this.labels.map((label) => label.id));
+ return JSON.stringify(this.labels.map((label) => getIdFromGraphQLId(label.id)));
},
assignees() {
- return this.issuable.assignees || [];
+ return this.issuable.assignees?.nodes || this.issuable.assignees || [];
},
createdAt() {
return sprintf(__('created %{timeAgo}'), {
@@ -81,6 +84,9 @@ export default {
});
},
updatedAt() {
+ if (!this.issuable.updatedAt) {
+ return '';
+ }
return sprintf(__('updated %{timeAgo}'), {
timeAgo: getTimeago().format(this.issuable.updatedAt),
});
@@ -157,7 +163,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 45584205be0..c13f80d2e31 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
@@ -2,6 +2,7 @@
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';
@@ -211,7 +212,7 @@ export default {
},
methods: {
issuableId(issuable) {
- return issuable.id || issuable.iid || uniqueId();
+ return getIdFromGraphQLId(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 8d00d337bac..70d73aca925 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,6 +42,9 @@ 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);
},
@@ -49,7 +52,7 @@ export default {
return isInPast(new Date(this.issue.dueDate));
},
timeEstimate() {
- return this.issue.timeStats?.humanTimeEstimate;
+ return this.issue.humanTimeEstimate || this.issue.timeStats?.humanTimeEstimate;
},
showHealthStatus() {
return this.hasIssuableHealthStatusFeature && this.issue.healthStatus;
@@ -85,7 +88,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 4ba2bc3a8e3..e53ef5f5c4b 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -9,24 +9,21 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
-import { toNumber } from 'lodash';
+import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
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_DESC,
+ RELATIVE_POSITION_ASC,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL,
@@ -37,19 +34,19 @@ import {
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_WEIGHT,
UPDATED_DESC,
- URL_PARAM,
urlSortParams,
} from '~/issues_list/constants';
import {
- convertToParams,
+ convertToApiParams,
convertToSearchQuery,
+ convertToUrlParams,
getDueDateValue,
getFilterTokens,
getSortKey,
getSortOptions,
} from '~/issues_list/utils';
import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/common_utils';
+import { getParameterByName } from '~/lib/utils/common_utils';
import {
DEFAULT_NONE_ANY,
OPERATOR_IS_ONLY,
@@ -107,9 +104,6 @@ export default {
emptyStateSvgPath: {
default: '',
},
- endpoint: {
- default: '',
- },
exportCsvPath: {
default: '',
},
@@ -173,15 +167,53 @@ export default {
dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
filterTokens: getFilterTokens(window.location.search),
- isLoading: false,
issues: [],
- page: toNumber(getParameterByName(PARAM_PAGE)) || 1,
+ page: 1,
+ pageInfo: {},
+ pageParams: {
+ firstPageSize: PAGE_SIZE,
+ },
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;
@@ -190,16 +222,22 @@ export default {
return this.showBulkEditSidebar || !this.issues.length;
},
isManualOrdering() {
- return this.sortKey === RELATIVE_POSITION_DESC;
+ return this.sortKey === RELATIVE_POSITION_ASC;
},
isOpenTab() {
return this.state === IssuableStates.Opened;
},
+ nextPage() {
+ return Number(this.pageInfo.hasNextPage);
+ },
+ previousPage() {
+ return Number(this.pageInfo.hasPreviousPage);
+ },
apiFilterParams() {
- return convertToParams(this.filterTokens, API_PARAM);
+ return convertToApiParams(this.filterTokens);
},
urlFilterParams() {
- return convertToParams(this.filterTokens, URL_PARAM);
+ return convertToUrlParams(this.filterTokens);
},
searchQuery() {
return convertToSearchQuery(this.filterTokens) || undefined;
@@ -214,6 +252,7 @@ export default {
dataType: 'user',
unique: true,
defaultAuthors: [],
+ operators: OPERATOR_IS_ONLY,
fetchAuthors: this.fetchUsers,
},
{
@@ -240,7 +279,7 @@ export default {
title: TOKEN_TITLE_LABEL,
icon: 'labels',
token: LabelToken,
- defaultLabels: [],
+ defaultLabels: DEFAULT_NONE_ANY,
fetchLabels: this.fetchLabels,
},
];
@@ -333,10 +372,9 @@ export default {
return {
due_date: this.dueDateFilter,
- page: this.page,
search: this.searchQuery,
+ sort: urlSortParams[this.sortKey],
state: this.state,
- ...urlSortParams[this.sortKey],
...filterParams,
};
},
@@ -346,7 +384,6 @@ export default {
},
mounted() {
eventHub.$on('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
- this.fetchIssues();
},
beforeDestroy() {
eventHub.$off('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
@@ -386,59 +423,19 @@ export default {
return this.fetchWithCache(this.projectMilestonesPath, 'milestones', 'title', search, true);
},
fetchIterations(search) {
- return axios.get(this.projectIterationsPath, { params: { search } });
+ const number = Number(search);
+ return !search || Number.isNaN(number)
+ ? axios.get(this.projectIterationsPath, { params: { search } })
+ : axios.get(this.projectIterationsPath, { params: { id: number } });
},
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.movedToId) {
+ if (issue.closedAt && issue.moved) {
return this.$options.i18n.closedMoved;
}
if (issue.closedAt) {
@@ -469,18 +466,30 @@ 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];
@@ -517,7 +526,6 @@ export default {
},
handleSort(value) {
this.sortKey = value;
- this.fetchIssues();
},
toggleBulkEditSidebar(showBulkEditSidebar) {
this.showBulkEditSidebar = showBulkEditSidebar;
@@ -541,14 +549,13 @@ export default {
:tabs="$options.IssuableListTabs"
:current-tab="state"
:tab-counts="tabCounts"
- :issuables-loading="isLoading"
+ :issuables-loading="$apollo.loading"
:is-manual-ordering="isManualOrdering"
:show-bulk-edit-sidebar="showBulkEditSidebar"
:show-pagination-controls="showPaginationControls"
- :total-items="totalIssues"
:current-page="page"
- :previous-page="page - 1"
- :next-page="page + 1"
+ :previous-page="previousPage"
+ :next-page="nextPage"
:url-params="urlParams"
@click-tab="handleClickTab"
@filter="handleFilter"
@@ -631,7 +638,7 @@ export default {
diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js
index 06e140d6420..414e7b756ef 100644
--- a/app/assets/javascripts/issues_list/constants.js
+++ b/app/assets/javascripts/issues_list/constants.js
@@ -101,7 +101,6 @@ 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';
@@ -125,21 +124,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_DESC = 'RELATIVE_POSITION_DESC';
+export const RELATIVE_POSITION_ASC = 'RELATIVE_POSITION_ASC';
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 SORT_ASC = 'asc';
-const SORT_DESC = 'desc';
-
+const PRIORITY_ASC_SORT = 'priority_asc';
const CREATED_DATE_SORT = 'created_date';
const CREATED_ASC_SORT = 'created_asc';
const UPDATED_DESC_SORT = 'updated_desc';
@@ -147,129 +146,30 @@ 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_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,
- },
+ [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,
};
export const MAX_LIST_SIZE = 10;
@@ -294,12 +194,7 @@ export const TOKEN_TYPE_WEIGHT = 'weight';
export const filters = {
[TOKEN_TYPE_AUTHOR]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'author_username',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[author_username]',
- },
+ [NORMAL_FILTER]: 'authorUsername',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -312,13 +207,8 @@ export const filters = {
},
[TOKEN_TYPE_ASSIGNEE]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'assignee_username',
- [SPECIAL_FILTER]: 'assignee_id',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[assignee_username]',
- },
+ [NORMAL_FILTER]: 'assigneeUsernames',
+ [SPECIAL_FILTER]: 'assigneeId',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -333,12 +223,7 @@ export const filters = {
},
[TOKEN_TYPE_MILESTONE]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'milestone',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[milestone]',
- },
+ [NORMAL_FILTER]: 'milestoneTitle',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -351,16 +236,13 @@ export const filters = {
},
[TOKEN_TYPE_LABEL]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'labels',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[labels]',
- },
+ [NORMAL_FILTER]: 'labelName',
+ [SPECIAL_FILTER]: 'labelName',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'label_name[]',
+ [SPECIAL_FILTER]: 'label_name[]',
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[label_name][]',
@@ -369,10 +251,8 @@ export const filters = {
},
[TOKEN_TYPE_MY_REACTION]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'my_reaction_emoji',
- [SPECIAL_FILTER]: 'my_reaction_emoji',
- },
+ [NORMAL_FILTER]: 'myReactionEmoji',
+ [SPECIAL_FILTER]: 'myReactionEmoji',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -383,9 +263,7 @@ export const filters = {
},
[TOKEN_TYPE_CONFIDENTIAL]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'confidential',
- },
+ [NORMAL_FILTER]: 'confidential',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -395,33 +273,23 @@ export const filters = {
},
[TOKEN_TYPE_ITERATION]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'iteration_title',
- [SPECIAL_FILTER]: 'iteration_id',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[iteration_title]',
- },
+ [NORMAL_FILTER]: 'iterationId',
+ [SPECIAL_FILTER]: 'iterationWildcardId',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
- [NORMAL_FILTER]: 'iteration_title',
+ [NORMAL_FILTER]: 'iteration_id',
[SPECIAL_FILTER]: 'iteration_id',
},
[OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[iteration_title]',
+ [NORMAL_FILTER]: 'not[iteration_id]',
},
},
},
[TOKEN_TYPE_EPIC]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'epic_id',
- [SPECIAL_FILTER]: 'epic_id',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[epic_id]',
- },
+ [NORMAL_FILTER]: 'epicId',
+ [SPECIAL_FILTER]: 'epicId',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
@@ -435,13 +303,8 @@ export const filters = {
},
[TOKEN_TYPE_WEIGHT]: {
[API_PARAM]: {
- [OPERATOR_IS]: {
- [NORMAL_FILTER]: 'weight',
- [SPECIAL_FILTER]: 'weight',
- },
- [OPERATOR_IS_NOT]: {
- [NORMAL_FILTER]: 'not[weight]',
- },
+ [NORMAL_FILTER]: 'weight',
+ [SPECIAL_FILTER]: 'weight',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js
index d0c9462a3d7..7d800caa33d 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues_list/index.js
@@ -73,6 +73,13 @@ export function mountIssuesListApp() {
return false;
}
+ Vue.use(VueApollo);
+
+ const defaultClient = createDefaultClient({}, { assumeImmutableResults: true });
+ const apolloProvider = new VueApollo({
+ defaultClient,
+ });
+
const {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
@@ -83,7 +90,6 @@ export function mountIssuesListApp() {
email,
emailsHelpPagePath,
emptyStateSvgPath,
- endpoint,
exportCsvPath,
groupEpicsPath,
hasBlockedIssuesFeature,
@@ -115,14 +121,13 @@ 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
new file mode 100644
index 00000000000..ded70d2d7ba
--- /dev/null
+++ b/app/assets/javascripts/issues_list/queries/get_issues.query.graphql
@@ -0,0 +1,45 @@
+#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
new file mode 100644
index 00000000000..c219dc8e35b
--- /dev/null
+++ b/app/assets/javascripts/issues_list/queries/issue_info.fragment.graphql
@@ -0,0 +1,51 @@
+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 b5ec44198da..23e5b70b353 100644
--- a/app/assets/javascripts/issues_list/utils.js
+++ b/app/assets/javascripts/issues_list/utils.js
@@ -1,4 +1,5 @@
import {
+ API_PARAM,
BLOCKING_ISSUES_DESC,
CREATED_ASC,
CREATED_DESC,
@@ -6,29 +7,36 @@ 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_DESC,
+ RELATIVE_POSITION_ASC,
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 } from '~/vue_shared/components/filtered_search_bar/constants';
+import {
+ FILTERED_SEARCH_TERM,
+ OPERATOR_IS_NOT,
+} from '~/vue_shared/components/filtered_search_bar/constants';
export const getSortKey = (sort) =>
- Object.keys(urlSortParams).find((key) => urlSortParams[key].sort === sort);
+ Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort);
export const getDueDateValue = (value) => (DUE_DATE_VALUES.includes(value) ? value : undefined);
@@ -38,7 +46,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: 1,
title: __('Priority'),
sortDirection: {
- ascending: PRIORITY_DESC,
+ ascending: PRIORITY_ASC,
descending: PRIORITY_DESC,
},
},
@@ -86,7 +94,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: 7,
title: __('Label priority'),
sortDirection: {
- ascending: LABEL_PRIORITY_DESC,
+ ascending: LABEL_PRIORITY_ASC,
descending: LABEL_PRIORITY_DESC,
},
},
@@ -94,8 +102,8 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: 8,
title: __('Manual'),
sortDirection: {
- ascending: RELATIVE_POSITION_DESC,
- descending: RELATIVE_POSITION_DESC,
+ ascending: RELATIVE_POSITION_ASC,
+ descending: RELATIVE_POSITION_ASC,
},
},
];
@@ -178,12 +186,36 @@ const getFilterType = (data, tokenType = '') =>
? SPECIAL_FILTER
: NORMAL_FILTER;
-export const convertToParams = (filterTokens, paramType) =>
+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) =>
filterTokens
.filter((token) => token.type !== FILTERED_SEARCH_TERM)
.reduce((acc, token) => {
const filterType = getFilterType(token.value.data, token.type);
- const param = filters[token.type][paramType][token.value.operator]?.[filterType];
+ const param = filters[token.type][URL_PARAM][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/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
index e5b26a00c4c..04efc459a21 100644
--- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -214,7 +214,7 @@ export default {
- {{ title }}
+ {{ title }}
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 1bb847217d1..214e1729bf8 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -141,7 +141,7 @@ export default {
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
index 886df94a7c4..abf690361cc 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
@@ -229,7 +229,7 @@ export default {
@expand-widget="expandWidget"
/>
-
+