Commit dbf848ce authored by Coung Ngo's avatar Coung Ngo Committed by Kushal Pandya

Convert filter searching to GraphQL in issues refactor

parent 91e3aeba
......@@ -11,6 +11,7 @@ import {
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import createFlash from '~/flash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
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';
......@@ -70,6 +71,10 @@ import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
import eventHub from '../eventhub';
import searchIterationsQuery from '../queries/search_iterations.query.graphql';
import searchLabelsQuery from '../queries/search_labels.query.graphql';
import searchMilestonesQuery from '../queries/search_milestones.query.graphql';
import searchUsersQuery from '../queries/search_users.query.graphql';
import IssueCardTimeInfo from './issue_card_time_info.vue';
export default {
......@@ -94,9 +99,6 @@ export default {
autocompleteAwardEmojisPath: {
default: '',
},
autocompleteUsersPath: {
default: '',
},
calendarPath: {
default: '',
},
......@@ -118,6 +120,9 @@ export default {
hasIssueWeightsFeature: {
default: false,
},
hasIterationsFeature: {
default: false,
},
hasMultipleIssueAssigneesFeature: {
default: false,
},
......@@ -139,15 +144,6 @@ export default {
newIssuePath: {
default: '',
},
projectIterationsPath: {
default: '',
},
projectLabelsPath: {
default: '',
},
projectMilestonesPath: {
default: '',
},
projectPath: {
default: '',
},
......@@ -233,7 +229,7 @@ export default {
if (gon.current_user_id) {
preloadedAuthors.push({
id: gon.current_user_id,
id: convertToGraphQLId('User', gon.current_user_id), // eslint-disable-line @gitlab/require-i18n-strings
name: gon.current_user_fullname,
username: gon.current_username,
avatar_url: gon.current_user_avatar_url,
......@@ -308,7 +304,7 @@ export default {
});
}
if (this.projectIterationsPath) {
if (this.hasIterationsFeature) {
tokens.push({
type: TOKEN_TYPE_ITERATION,
title: TOKEN_TITLE_ITERATION,
......@@ -407,19 +403,42 @@ export default {
: epics.filter((epic) => epic.id === number);
},
fetchLabels(search) {
return this.fetchWithCache(this.projectLabelsPath, 'labels', 'title', search);
return this.$apollo
.query({
query: searchLabelsQuery,
variables: { projectPath: this.projectPath, search },
})
.then(({ data }) => data.project.labels.nodes);
},
fetchMilestones(search) {
return this.fetchWithCache(this.projectMilestonesPath, 'milestones', 'title', search, true);
return this.$apollo
.query({
query: searchMilestonesQuery,
variables: { projectPath: this.projectPath, search },
})
.then(({ data }) => data.project.milestones.nodes);
},
fetchIterations(search) {
const id = Number(search);
return !search || Number.isNaN(id)
? axios.get(this.projectIterationsPath, { params: { search } })
: axios.get(this.projectIterationsPath, { params: { id } });
const variables =
!search || Number.isNaN(id)
? { projectPath: this.projectPath, search }
: { projectPath: this.projectPath, id };
return this.$apollo
.query({
query: searchIterationsQuery,
variables,
})
.then(({ data }) => data.project.iterations.nodes);
},
fetchUsers(search) {
return axios.get(this.autocompleteUsersPath, { params: { search } });
return this.$apollo
.query({
query: searchUsersQuery,
variables: { projectPath: this.projectPath, search },
})
.then(({ data }) => data.project.projectMembers.nodes.map((member) => member.user));
},
getExportCsvPathWithQuery() {
return `${this.exportCsvPath}${window.location.search}`;
......
......@@ -82,7 +82,6 @@ export function mountIssuesListApp() {
const {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
calendarPath,
canBulkUpdate,
canEdit,
......@@ -95,6 +94,7 @@ export function mountIssuesListApp() {
hasBlockedIssuesFeature,
hasIssuableHealthStatusFeature,
hasIssueWeightsFeature,
hasIterationsFeature,
hasMultipleIssueAssigneesFeature,
hasProjectIssues,
importCsvIssuesPath,
......@@ -106,9 +106,6 @@ export function mountIssuesListApp() {
maxAttachmentSize,
newIssuePath,
projectImportJiraPath,
projectIterationsPath,
projectLabelsPath,
projectMilestonesPath,
projectPath,
quickActionsHelpPath,
resetPath,
......@@ -122,7 +119,6 @@ export function mountIssuesListApp() {
apolloProvider,
provide: {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
calendarPath,
canBulkUpdate: parseBoolean(canBulkUpdate),
emptyStateSvgPath,
......@@ -130,15 +126,13 @@ export function mountIssuesListApp() {
hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
hasIterationsFeature: parseBoolean(hasIterationsFeature),
hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature),
hasProjectIssues: parseBoolean(hasProjectIssues),
isSignedIn: parseBoolean(isSignedIn),
issuesPath,
jiraIntegrationPath,
newIssuePath,
projectIterationsPath,
projectLabelsPath,
projectMilestonesPath,
projectPath,
rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink),
......
query searchIterations($projectPath: ID!, $search: String, $id: ID) {
project(fullPath: $projectPath) {
iterations(title: $search, id: $id) {
nodes {
id
title
}
}
}
}
query searchLabels($projectPath: ID!, $search: String) {
project(fullPath: $projectPath) {
labels(searchTerm: $search, includeAncestorGroups: true) {
nodes {
id
color
textColor
title
}
}
}
}
query searchMilestones($projectPath: ID!, $search: String) {
project(fullPath: $projectPath) {
milestones(searchTitle: $search, includeAncestors: true) {
nodes {
id
title
}
}
}
}
query searchUsers($projectPath: ID!, $search: String) {
project(fullPath: $projectPath) {
projectMembers(search: $search) {
nodes {
user {
id
avatarUrl
name
username
}
}
}
}
}
......@@ -6,6 +6,7 @@ import {
GlDropdownSectionHeader,
GlLoadingIcon,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { DEBOUNCE_DELAY } from '../constants';
import { getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
......@@ -128,12 +129,12 @@ export default {
},
},
methods: {
handleInput({ data }) {
handleInput: debounce(function debouncedSearch({ data }) {
this.searchKey = data;
setTimeout(() => {
if (!this.suggestionsLoading) this.$emit('fetch-suggestions', data);
}, DEBOUNCE_DELAY);
},
if (!this.suggestionsLoading) {
this.$emit('fetch-suggestions', data);
}
}, DEBOUNCE_DELAY),
handleTokenValueSelected(activeTokenValue) {
// Make sure that;
// 1. Recently used values feature is enabled
......
......@@ -7,6 +7,7 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import { DEBOUNCE_DELAY, DEFAULT_ITERATIONS } from '../constants';
......@@ -30,7 +31,7 @@ export default {
data() {
return {
iterations: this.config.initialIterations || [],
loading: true,
loading: false,
};
},
computed: {
......@@ -38,7 +39,9 @@ export default {
return this.value.data;
},
activeIteration() {
return this.iterations.find((iteration) => iteration.id === Number(this.currentValue));
return this.iterations.find(
(iteration) => getIdFromGraphQLId(iteration.id) === Number(this.currentValue),
);
},
defaultIterations() {
return this.config.defaultIterations || DEFAULT_ITERATIONS;
......@@ -55,6 +58,9 @@ export default {
},
},
methods: {
getValue(iteration) {
return String(getIdFromGraphQLId(iteration.id));
},
fetchIterationBySearchTerm(searchTerm) {
const fetchPromise = this.config.fetchPath
? this.config.fetchIterations(this.config.fetchPath, searchTerm)
......@@ -102,7 +108,7 @@ export default {
<gl-filtered-search-suggestion
v-for="iteration in iterations"
:key="iteration.id"
:value="String(iteration.id)"
:value="getValue(iteration)"
>
{{ iteration.title }}
</gl-filtered-search-suggestion>
......
......@@ -35,7 +35,7 @@ export default {
return {
milestones: this.config.initialMilestones || [],
defaultMilestones: this.config.defaultMilestones || DEFAULT_MILESTONES,
loading: true,
loading: false,
};
},
computed: {
......@@ -60,11 +60,16 @@ export default {
},
methods: {
fetchMilestoneBySearchTerm(searchTerm = '') {
if (this.loading) {
return;
}
this.loading = true;
this.config
.fetchMilestones(searchTerm)
.then(({ data }) => {
this.milestones = data.sort(sortMilestonesByDueDate);
.then((response) => {
const data = Array.isArray(response) ? response : response.data;
this.milestones = data.slice().sort(sortMilestonesByDueDate);
})
.catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
.finally(() => {
......
......@@ -181,7 +181,6 @@ module IssuesHelper
def issues_list_data(project, current_user, finder)
{
autocomplete_users_path: autocomplete_users_path(active: true, current_user: true, project_id: project.id, format: :json),
autocomplete_award_emojis_path: autocomplete_award_emojis_path,
calendar_path: url_for(safe_params.merge(calendar_url_options)),
can_bulk_update: can?(current_user, :admin_issue, project).to_s,
......@@ -201,8 +200,6 @@ module IssuesHelper
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project),
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
project_milestones_path: project_milestones_path(project, format: :json),
project_path: project.full_path,
quick_actions_help_path: help_page_path('user/project/quick_actions'),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
......
......@@ -48,6 +48,7 @@ module EE
has_blocked_issues_feature: project.feature_available?(:blocked_issues).to_s,
has_issuable_health_status_feature: project.feature_available?(:issuable_health_status).to_s,
has_issue_weights_feature: project.feature_available?(:issue_weights).to_s,
has_iterations_feature: project.feature_available?(:iterations).to_s,
has_multiple_issue_assignees_feature: project.feature_available?(:multiple_issue_assignees).to_s
)
......@@ -55,10 +56,6 @@ module EE
data[:group_epics_path] = group_epics_path(project.group, format: :json)
end
if project.feature_available?(:iterations)
data[:project_iterations_path] = api_v4_projects_iterations_path(id: project.id)
end
data
end
end
......
......@@ -145,9 +145,9 @@ RSpec.describe EE::IssuesHelper do
has_blocked_issues_feature: 'true',
has_issuable_health_status_feature: 'true',
has_issue_weights_feature: 'true',
has_iterations_feature: 'true',
has_multiple_issue_assignees_feature: 'true',
group_epics_path: group_epics_path(project.group, format: :json),
project_iterations_path: api_v4_projects_iterations_path(id: project.id)
group_epics_path: group_epics_path(project.group, format: :json)
}
expect(helper.issues_list_data(project, current_user, finder)).to include(expected)
......@@ -172,6 +172,7 @@ RSpec.describe EE::IssuesHelper do
has_blocked_issues_feature: 'false',
has_issuable_health_status_feature: 'false',
has_issue_weights_feature: 'false',
has_iterations_feature: 'false',
has_multiple_issue_assignees_feature: 'false'
}
......@@ -179,7 +180,6 @@ RSpec.describe EE::IssuesHelper do
expect(result).to include(expected)
expect(result).not_to include(:group_epics_path)
expect(result).not_to include(:project_iterations_path)
end
end
end
......
......@@ -15,6 +15,7 @@ import {
urlParams,
} from 'jest/issues_list/mock_data';
import createFlash from '~/flash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
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';
......@@ -54,19 +55,18 @@ describe('IssuesListApp component', () => {
localVue.use(VueApollo);
const defaultProvide = {
autocompleteUsersPath: 'autocomplete/users/path',
calendarPath: 'calendar/path',
canBulkUpdate: false,
emptyStateSvgPath: 'empty-state.svg',
exportCsvPath: 'export/csv/path',
hasBlockedIssuesFeature: true,
hasIssueWeightsFeature: true,
hasIterationsFeature: true,
hasProjectIssues: true,
isSignedIn: false,
issuesPath: 'path/to/issues',
jiraIntegrationPath: 'jira/integration/path',
newIssuePath: 'new/issue/path',
projectLabelsPath: 'project/labels/path',
projectPath: 'path/to/project',
rssPath: 'rss/path',
showNewIssueLink: true,
......@@ -545,9 +545,13 @@ describe('IssuesListApp component', () => {
});
it('renders all tokens', () => {
const preloadedAuthors = [
{ ...mockCurrentUser, id: convertToGraphQLId('User', mockCurrentUser.id) },
];
expect(findIssuableList().props('searchTokens')).toMatchObject([
{ type: TOKEN_TYPE_AUTHOR, preloadedAuthors: [mockCurrentUser] },
{ type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors: [mockCurrentUser] },
{ type: TOKEN_TYPE_AUTHOR, preloadedAuthors },
{ type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors },
{ type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_MY_REACTION },
......
......@@ -294,7 +294,6 @@ RSpec.describe IssuesHelper do
expected = {
autocomplete_award_emojis_path: autocomplete_award_emojis_path,
autocomplete_users_path: autocomplete_users_path(active: true, current_user: true, project_id: project.id, format: :json),
calendar_path: '#',
can_bulk_update: 'true',
can_edit: 'true',
......@@ -313,8 +312,6 @@ RSpec.describe IssuesHelper do
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project),
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
project_milestones_path: project_milestones_path(project, format: :json),
project_path: project.full_path,
quick_actions_help_path: help_page_path('user/project/quick_actions'),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
......
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