From f31ef3fd5548f9ffd5740b6aaca8672c27e34b42 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 11 Jan 2022 06:10:58 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../issuable_bulk_update_sidebar.js | 2 +- .../list/components/issue_card_time_info.vue | 104 +++ .../issues/list/components/issues_list_app.vue | 821 ++++++++++++++++++++ .../components/jira_issues_import_status_app.vue | 112 +++ .../issues/list/components/new_issue_dropdown.vue | 127 ++++ app/assets/javascripts/issues/list/constants.js | 316 ++++++++ app/assets/javascripts/issues/list/eventhub.js | 3 + app/assets/javascripts/issues/list/index.js | 165 ++++ .../issues/list/queries/get_issues.query.graphql | 90 +++ .../list/queries/get_issues_counts.query.graphql | 129 ++++ .../queries/get_issues_list_details.query.graphql | 24 + .../issues/list/queries/issue.fragment.graphql | 54 ++ .../issues/list/queries/label.fragment.graphql | 6 + .../issues/list/queries/milestone.fragment.graphql | 4 + .../list/queries/reorder_issues.mutation.graphql | 13 + .../list/queries/search_labels.query.graphql | 20 + .../list/queries/search_milestones.query.graphql | 20 + .../list/queries/search_projects.query.graphql | 14 + .../issues/list/queries/search_users.query.graphql | 26 + .../issues/list/queries/user.fragment.graphql | 6 + app/assets/javascripts/issues/list/utils.js | 261 +++++++ .../components/issue_card_time_info.vue | 104 --- .../issues_list/components/issues_list_app.vue | 821 -------------------- .../components/jira_issues_import_status_app.vue | 112 --- .../issues_list/components/new_issue_dropdown.vue | 127 ---- app/assets/javascripts/issues_list/constants.js | 316 -------- app/assets/javascripts/issues_list/eventhub.js | 3 - app/assets/javascripts/issues_list/index.js | 165 ---- .../issues_list/queries/get_issues.query.graphql | 90 --- .../queries/get_issues_counts.query.graphql | 129 ---- .../queries/get_issues_list_details.query.graphql | 24 - .../issues_list/queries/issue.fragment.graphql | 54 -- .../issues_list/queries/label.fragment.graphql | 6 - .../issues_list/queries/milestone.fragment.graphql | 4 - .../queries/reorder_issues.mutation.graphql | 13 - .../queries/search_labels.query.graphql | 20 - .../queries/search_milestones.query.graphql | 20 - .../queries/search_projects.query.graphql | 14 - .../issues_list/queries/search_users.query.graphql | 26 - .../issues_list/queries/user.fragment.graphql | 6 - app/assets/javascripts/issues_list/utils.js | 261 ------- .../javascripts/pages/groups/issues/index.js | 2 +- .../pages/projects/issues/index/index.js | 2 +- .../components/mr_widget_status_icon.vue | 4 +- .../components/states/merge_checks_failed.vue | 45 +- .../components/states/mr_widget_archived.vue | 11 +- .../components/states/mr_widget_checking.vue | 6 +- .../components/states/mr_widget_conflicts.vue | 10 +- .../components/states/mr_widget_missing_branch.vue | 15 +- .../states/mr_widget_pipeline_blocked.vue | 4 +- .../components/states/mr_widget_rebase.vue | 15 + .../components/states/pipeline_failed.vue | 4 +- .../components/states/ready_to_merge.vue | 2 +- .../components/states/sha_mismatch.vue | 8 +- .../components/states/unresolved_discussions.vue | 22 +- .../components/states/work_in_progress.vue | 5 +- .../stores/mr_widget_store.js | 10 +- app/models/hooks/project_hook.rb | 5 + app/models/hooks/service_hook.rb | 4 + app/models/hooks/web_hook.rb | 5 + app/services/web_hook_service.rb | 41 +- app/workers/web_hook_worker.rb | 12 +- .../development/webhook_recursion_detection.yml | 8 + config/initializers/webhook_recursion_detection.rb | 5 + .../operations/moving_repositories.md | 50 +- doc/user/application_security/dast/index.md | 2 +- doc/user/project/members/index.md | 2 +- .../project/members/share_project_with_groups.md | 2 +- lib/api/ci/triggers.rb | 2 +- lib/backup/gitaly_backup.rb | 47 +- lib/backup/gitaly_rpc_backup.rb | 2 +- lib/backup/repositories.rb | 4 +- .../middleware/webhook_recursion_detection.rb | 19 + lib/gitlab/web_hooks.rb | 7 + lib/gitlab/web_hooks/recursion_detection.rb | 102 +++ lib/gitlab/web_hooks/recursion_detection/uuid.rb | 46 ++ lib/tasks/gitlab/backup.rake | 2 +- lib/tasks/gitlab/gitaly.rake | 38 + locale/gitlab.pot | 6 +- scripts/gitaly-test-build | 2 + scripts/gitaly-test-spawn | 13 +- .../list/components/issue_card_time_info_spec.js | 122 +++ .../issues/list/components/issues_list_app_spec.js | 829 +++++++++++++++++++++ .../jira_issues_import_status_app_spec.js | 117 +++ .../list/components/new_issue_dropdown_spec.js | 131 ++++ spec/frontend/issues/list/mock_data.js | 310 ++++++++ spec/frontend/issues/list/utils_spec.js | 127 ++++ .../components/issue_card_time_info_spec.js | 122 --- .../issues_list/components/issues_list_app_spec.js | 829 --------------------- .../jira_issues_import_status_app_spec.js | 117 --- .../components/new_issue_dropdown_spec.js | 131 ---- spec/frontend/issues_list/mock_data.js | 310 -------- spec/frontend/issues_list/utils_spec.js | 127 ---- .../components/states/merge_checks_failed_spec.js | 29 +- spec/lib/backup/gitaly_backup_spec.rb | 32 +- spec/lib/backup/gitaly_rpc_backup_spec.rb | 10 +- spec/lib/backup/repositories_spec.rb | 12 +- .../middleware/webhook_recursion_detection_spec.rb | 42 ++ .../gitlab/web_hooks/recursion_detection_spec.rb | 243 ++++++ spec/models/hooks/project_hook_spec.rb | 9 + spec/models/hooks/service_hook_spec.rb | 30 + spec/models/integrations/datadog_spec.rb | 4 +- spec/requests/api/ci/triggers_spec.rb | 2 +- spec/requests/recursive_webhook_detection_spec.rb | 200 +++++ spec/services/web_hook_service_spec.rb | 139 +++- spec/support/helpers/gitaly_setup.rb | 131 +--- spec/support/helpers/test_env.rb | 117 ++- spec/support/praefect.rb | 4 +- spec/tasks/gitlab/backup_rake_spec.rb | 2 +- spec/workers/web_hook_worker_spec.rb | 9 + 110 files changed, 5219 insertions(+), 4291 deletions(-) create mode 100644 app/assets/javascripts/issues/list/components/issue_card_time_info.vue create mode 100644 app/assets/javascripts/issues/list/components/issues_list_app.vue create mode 100644 app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue create mode 100644 app/assets/javascripts/issues/list/components/new_issue_dropdown.vue create mode 100644 app/assets/javascripts/issues/list/constants.js create mode 100644 app/assets/javascripts/issues/list/eventhub.js create mode 100644 app/assets/javascripts/issues/list/index.js create mode 100644 app/assets/javascripts/issues/list/queries/get_issues.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/get_issues_list_details.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/issue.fragment.graphql create mode 100644 app/assets/javascripts/issues/list/queries/label.fragment.graphql create mode 100644 app/assets/javascripts/issues/list/queries/milestone.fragment.graphql create mode 100644 app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql create mode 100644 app/assets/javascripts/issues/list/queries/search_labels.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/search_milestones.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/search_projects.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/search_users.query.graphql create mode 100644 app/assets/javascripts/issues/list/queries/user.fragment.graphql create mode 100644 app/assets/javascripts/issues/list/utils.js delete mode 100644 app/assets/javascripts/issues_list/components/issue_card_time_info.vue delete mode 100644 app/assets/javascripts/issues_list/components/issues_list_app.vue delete mode 100644 app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue delete mode 100644 app/assets/javascripts/issues_list/components/new_issue_dropdown.vue delete mode 100644 app/assets/javascripts/issues_list/constants.js delete mode 100644 app/assets/javascripts/issues_list/eventhub.js delete mode 100644 app/assets/javascripts/issues_list/index.js delete mode 100644 app/assets/javascripts/issues_list/queries/get_issues.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/get_issues_list_details.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/issue.fragment.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/label.fragment.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/milestone.fragment.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/search_labels.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/search_milestones.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/search_projects.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/search_users.query.graphql delete mode 100644 app/assets/javascripts/issues_list/queries/user.fragment.graphql delete mode 100644 app/assets/javascripts/issues_list/utils.js create mode 100644 config/feature_flags/development/webhook_recursion_detection.yml create mode 100644 config/initializers/webhook_recursion_detection.rb create mode 100644 lib/gitlab/middleware/webhook_recursion_detection.rb create mode 100644 lib/gitlab/web_hooks.rb create mode 100644 lib/gitlab/web_hooks/recursion_detection.rb create mode 100644 lib/gitlab/web_hooks/recursion_detection/uuid.rb create mode 100644 spec/frontend/issues/list/components/issue_card_time_info_spec.js create mode 100644 spec/frontend/issues/list/components/issues_list_app_spec.js create mode 100644 spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js create mode 100644 spec/frontend/issues/list/components/new_issue_dropdown_spec.js create mode 100644 spec/frontend/issues/list/mock_data.js create mode 100644 spec/frontend/issues/list/utils_spec.js delete mode 100644 spec/frontend/issues_list/components/issue_card_time_info_spec.js delete mode 100644 spec/frontend/issues_list/components/issues_list_app_spec.js delete mode 100644 spec/frontend/issues_list/components/jira_issues_import_status_app_spec.js delete mode 100644 spec/frontend/issues_list/components/new_issue_dropdown_spec.js delete mode 100644 spec/frontend/issues_list/mock_data.js delete mode 100644 spec/frontend/issues_list/utils_spec.js create mode 100644 spec/lib/gitlab/middleware/webhook_recursion_detection_spec.rb create mode 100644 spec/lib/gitlab/web_hooks/recursion_detection_spec.rb create mode 100644 spec/requests/recursive_webhook_detection_spec.rb diff --git a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js index 42ac7d4ef9f..d46354e240a 100644 --- a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js @@ -1,7 +1,7 @@ /* eslint-disable class-methods-use-this, no-new */ import $ from 'jquery'; -import issuableEventHub from '~/issues_list/eventhub'; +import issuableEventHub from '~/issues/list/eventhub'; import LabelsSelect from '~/labels/labels_select'; import MilestoneSelect from '~/milestones/milestone_select'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; 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 new file mode 100644 index 00000000000..aece7372182 --- /dev/null +++ b/app/assets/javascripts/issues/list/components/issue_card_time_info.vue @@ -0,0 +1,104 @@ + + + diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue new file mode 100644 index 00000000000..8b15e801f02 --- /dev/null +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -0,0 +1,821 @@ + + + diff --git a/app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue b/app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue new file mode 100644 index 00000000000..fb1dbef666c --- /dev/null +++ b/app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue @@ -0,0 +1,112 @@ + + + diff --git a/app/assets/javascripts/issues/list/components/new_issue_dropdown.vue b/app/assets/javascripts/issues/list/components/new_issue_dropdown.vue new file mode 100644 index 00000000000..71f84050ba8 --- /dev/null +++ b/app/assets/javascripts/issues/list/components/new_issue_dropdown.vue @@ -0,0 +1,127 @@ + + + diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js new file mode 100644 index 00000000000..4a380848b4f --- /dev/null +++ b/app/assets/javascripts/issues/list/constants.js @@ -0,0 +1,316 @@ +import { __, s__ } from '~/locale'; +import { + FILTER_ANY, + FILTER_CURRENT, + FILTER_NONE, + FILTER_STARTED, + FILTER_UPCOMING, + OPERATOR_IS, + OPERATOR_IS_NOT, +} from '~/vue_shared/components/filtered_search_bar/constants'; + +export const i18n = { + anonymousSearchingMessage: __('You must sign in to search for specific terms.'), + calendarLabel: __('Subscribe to calendar'), + closed: __('CLOSED'), + closedMoved: __('CLOSED (MOVED)'), + confidentialNo: __('No'), + confidentialYes: __('Yes'), + downvotes: __('Downvotes'), + editIssues: __('Edit issues'), + errorFetchingCounts: __('An error occurred while getting issue counts'), + errorFetchingIssues: __('An error occurred while loading issues'), + issueRepositioningMessage: __( + 'Issues are being rebalanced at the moment, so manual reordering is disabled.', + ), + jiraIntegrationMessage: s__( + 'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.', + ), + jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'), + jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'), + newIssueLabel: __('New issue'), + noClosedIssuesTitle: __('There are no closed issues'), + noOpenIssuesDescription: __('To keep this project going, create a new issue'), + noOpenIssuesTitle: __('There are no open issues'), + noIssuesSignedInDescription: __( + 'Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.', + ), + noIssuesSignedInTitle: __( + 'The Issue Tracker is the place to add things that need to be improved or solved in a project', + ), + noIssuesSignedOutButtonText: __('Register / Sign In'), + noIssuesSignedOutDescription: __( + 'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.', + ), + noIssuesSignedOutTitle: __('There are no issues to show'), + noSearchResultsDescription: __('To widen your search, change or remove filters above'), + noSearchResultsTitle: __('Sorry, your filter produced no results'), + relatedMergeRequests: __('Related merge requests'), + reorderError: __('An error occurred while reordering issues.'), + rssLabel: __('Subscribe to RSS feed'), + searchPlaceholder: __('Search or filter results...'), + upvotes: __('Upvotes'), +}; + +export const MAX_LIST_SIZE = 10; +export const PAGE_SIZE = 20; +export const PAGE_SIZE_MANUAL = 100; +export const PARAM_DUE_DATE = 'due_date'; +export const PARAM_SORT = 'sort'; +export const PARAM_STATE = 'state'; +export const RELATIVE_POSITION = 'relative_position'; + +export const defaultPageSizeParams = { + firstPageSize: PAGE_SIZE, +}; + +export const largePageSizeParams = { + firstPageSize: PAGE_SIZE_MANUAL, +}; + +export const DUE_DATE_NONE = '0'; +export const DUE_DATE_ANY = ''; +export const DUE_DATE_OVERDUE = 'overdue'; +export const DUE_DATE_WEEK = 'week'; +export const DUE_DATE_MONTH = 'month'; +export const DUE_DATE_NEXT_MONTH_AND_PREVIOUS_TWO_WEEKS = 'next_month_and_previous_two_weeks'; +export const DUE_DATE_VALUES = [ + DUE_DATE_NONE, + DUE_DATE_ANY, + DUE_DATE_OVERDUE, + DUE_DATE_WEEK, + DUE_DATE_MONTH, + DUE_DATE_NEXT_MONTH_AND_PREVIOUS_TWO_WEEKS, +]; + +export const BLOCKING_ISSUES_ASC = 'BLOCKING_ISSUES_ASC'; +export const BLOCKING_ISSUES_DESC = 'BLOCKING_ISSUES_DESC'; +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 TITLE_ASC = 'TITLE_ASC'; +export const TITLE_DESC = 'TITLE_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'; + +export const urlSortParams = { + [PRIORITY_ASC]: 'priority', + [PRIORITY_DESC]: 'priority_desc', + [CREATED_ASC]: 'created_asc', + [CREATED_DESC]: 'created_date', + [UPDATED_ASC]: 'updated_asc', + [UPDATED_DESC]: 'updated_desc', + [MILESTONE_DUE_ASC]: 'milestone', + [MILESTONE_DUE_DESC]: 'milestone_due_desc', + [DUE_DATE_ASC]: 'due_date', + [DUE_DATE_DESC]: 'due_date_desc', + [POPULARITY_ASC]: 'popularity_asc', + [POPULARITY_DESC]: 'popularity', + [LABEL_PRIORITY_ASC]: 'label_priority', + [LABEL_PRIORITY_DESC]: 'label_priority_desc', + [RELATIVE_POSITION_ASC]: RELATIVE_POSITION, + [WEIGHT_ASC]: 'weight', + [WEIGHT_DESC]: 'weight_desc', + [BLOCKING_ISSUES_ASC]: 'blocking_issues_asc', + [BLOCKING_ISSUES_DESC]: 'blocking_issues_desc', + [TITLE_ASC]: 'title_asc', + [TITLE_DESC]: 'title_desc', +}; + +export const API_PARAM = 'apiParam'; +export const URL_PARAM = 'urlParam'; +export const NORMAL_FILTER = 'normalFilter'; +export const SPECIAL_FILTER = 'specialFilter'; +export const ALTERNATIVE_FILTER = 'alternativeFilter'; +export const SPECIAL_FILTER_VALUES = [ + FILTER_NONE, + FILTER_ANY, + FILTER_CURRENT, + FILTER_UPCOMING, + FILTER_STARTED, +]; + +export const TOKEN_TYPE_AUTHOR = 'author_username'; +export const TOKEN_TYPE_ASSIGNEE = 'assignee_username'; +export const TOKEN_TYPE_MILESTONE = 'milestone'; +export const TOKEN_TYPE_LABEL = 'labels'; +export const TOKEN_TYPE_TYPE = 'type'; +export const TOKEN_TYPE_RELEASE = 'release'; +export const TOKEN_TYPE_MY_REACTION = 'my_reaction_emoji'; +export const TOKEN_TYPE_CONFIDENTIAL = 'confidential'; +export const TOKEN_TYPE_ITERATION = 'iteration'; +export const TOKEN_TYPE_EPIC = 'epic_id'; +export const TOKEN_TYPE_WEIGHT = 'weight'; + +export const filters = { + [TOKEN_TYPE_AUTHOR]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'authorUsername', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'author_username', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[author_username]', + }, + }, + }, + [TOKEN_TYPE_ASSIGNEE]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'assigneeUsernames', + [SPECIAL_FILTER]: 'assigneeId', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'assignee_username[]', + [SPECIAL_FILTER]: 'assignee_id', + [ALTERNATIVE_FILTER]: 'assignee_username', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[assignee_username][]', + }, + }, + }, + [TOKEN_TYPE_MILESTONE]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'milestoneTitle', + [SPECIAL_FILTER]: 'milestoneWildcardId', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'milestone_title', + [SPECIAL_FILTER]: 'milestone_title', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[milestone_title]', + }, + }, + }, + [TOKEN_TYPE_LABEL]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'labelName', + [SPECIAL_FILTER]: 'labelName', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'label_name[]', + [SPECIAL_FILTER]: 'label_name[]', + [ALTERNATIVE_FILTER]: 'label_name', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[label_name][]', + }, + }, + }, + [TOKEN_TYPE_TYPE]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'types', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'type[]', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[type][]', + }, + }, + }, + [TOKEN_TYPE_RELEASE]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'releaseTag', + [SPECIAL_FILTER]: 'releaseTagWildcardId', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'release_tag', + [SPECIAL_FILTER]: 'release_tag', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[release_tag]', + }, + }, + }, + [TOKEN_TYPE_MY_REACTION]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'myReactionEmoji', + [SPECIAL_FILTER]: 'myReactionEmoji', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'my_reaction_emoji', + [SPECIAL_FILTER]: 'my_reaction_emoji', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[my_reaction_emoji]', + }, + }, + }, + [TOKEN_TYPE_CONFIDENTIAL]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'confidential', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'confidential', + }, + }, + }, + [TOKEN_TYPE_ITERATION]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'iterationId', + [SPECIAL_FILTER]: 'iterationWildcardId', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'iteration_id', + [SPECIAL_FILTER]: 'iteration_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[iteration_id]', + }, + }, + }, + [TOKEN_TYPE_EPIC]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'epicId', + [SPECIAL_FILTER]: 'epicId', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'epic_id', + [SPECIAL_FILTER]: 'epic_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[epic_id]', + }, + }, + }, + [TOKEN_TYPE_WEIGHT]: { + [API_PARAM]: { + [NORMAL_FILTER]: 'weight', + [SPECIAL_FILTER]: 'weight', + }, + [URL_PARAM]: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'weight', + [SPECIAL_FILTER]: 'weight', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[weight]', + }, + }, + }, +}; diff --git a/app/assets/javascripts/issues/list/eventhub.js b/app/assets/javascripts/issues/list/eventhub.js new file mode 100644 index 00000000000..e31806ad199 --- /dev/null +++ b/app/assets/javascripts/issues/list/eventhub.js @@ -0,0 +1,3 @@ +import createEventHub from '~/helpers/event_hub_factory'; + +export default createEventHub(); diff --git a/app/assets/javascripts/issues/list/index.js b/app/assets/javascripts/issues/list/index.js new file mode 100644 index 00000000000..01cc82ed8fd --- /dev/null +++ b/app/assets/javascripts/issues/list/index.js @@ -0,0 +1,165 @@ +import produce from 'immer'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql'; +import IssuesListApp from 'ee_else_ce/issues/list/components/issues_list_app.vue'; +import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import JiraIssuesImportStatusRoot from './components/jira_issues_import_status_app.vue'; + +export function mountJiraIssuesListApp() { + const el = document.querySelector('.js-jira-issues-import-status'); + + if (!el) { + return false; + } + + const { issuesPath, projectPath } = el.dataset; + const canEdit = parseBoolean(el.dataset.canEdit); + const isJiraConfigured = parseBoolean(el.dataset.isJiraConfigured); + + if (!isJiraConfigured || !canEdit) { + return false; + } + + Vue.use(VueApollo); + const defaultClient = createDefaultClient(); + const apolloProvider = new VueApollo({ + defaultClient, + }); + + return new Vue({ + el, + apolloProvider, + render(createComponent) { + return createComponent(JiraIssuesImportStatusRoot, { + props: { + canEdit, + isJiraConfigured, + issuesPath, + projectPath, + }, + }); + }, + }); +} + +export function mountIssuesListApp() { + const el = document.querySelector('.js-issues-list'); + + if (!el) { + return false; + } + + Vue.use(VueApollo); + + const resolvers = { + Mutation: { + reorderIssues: (_, { oldIndex, newIndex, namespace, serializedVariables }, { cache }) => { + const variables = JSON.parse(serializedVariables); + const sourceData = cache.readQuery({ query: getIssuesQuery, variables }); + + const data = produce(sourceData, (draftData) => { + const issues = draftData[namespace].issues.nodes.slice(); + const issueToMove = issues[oldIndex]; + issues.splice(oldIndex, 1); + issues.splice(newIndex, 0, issueToMove); + + draftData[namespace].issues.nodes = issues; + }); + + cache.writeQuery({ query: getIssuesQuery, variables, data }); + }, + }, + }; + + const defaultClient = createDefaultClient(resolvers); + const apolloProvider = new VueApollo({ + defaultClient, + }); + + const { + autocompleteAwardEmojisPath, + calendarPath, + canBulkUpdate, + canEdit, + canImportIssues, + email, + emailsHelpPagePath, + emptyStateSvgPath, + exportCsvPath, + fullPath, + groupPath, + hasAnyIssues, + hasAnyProjects, + hasBlockedIssuesFeature, + hasIssuableHealthStatusFeature, + hasIssueWeightsFeature, + hasIterationsFeature, + hasMultipleIssueAssigneesFeature, + importCsvIssuesPath, + initialEmail, + isAnonymousSearchDisabled, + isIssueRepositioningDisabled, + isProject, + isSignedIn, + jiraIntegrationPath, + markdownHelpPath, + maxAttachmentSize, + newIssuePath, + projectImportJiraPath, + quickActionsHelpPath, + releasesPath, + resetPath, + rssPath, + showNewIssueLink, + signInPath, + } = el.dataset; + + return new Vue({ + el, + apolloProvider, + provide: { + autocompleteAwardEmojisPath, + calendarPath, + canBulkUpdate: parseBoolean(canBulkUpdate), + emptyStateSvgPath, + fullPath, + groupPath, + hasAnyIssues: parseBoolean(hasAnyIssues), + hasAnyProjects: parseBoolean(hasAnyProjects), + hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature), + hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature), + hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), + hasIterationsFeature: parseBoolean(hasIterationsFeature), + hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature), + isAnonymousSearchDisabled: parseBoolean(isAnonymousSearchDisabled), + isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled), + isProject: parseBoolean(isProject), + isSignedIn: parseBoolean(isSignedIn), + jiraIntegrationPath, + newIssuePath, + releasesPath, + rssPath, + showNewIssueLink: parseBoolean(showNewIssueLink), + signInPath, + // For CsvImportExportButtons component + canEdit: parseBoolean(canEdit), + email, + exportCsvPath, + importCsvIssuesPath, + maxAttachmentSize, + projectImportJiraPath, + showExportButton: parseBoolean(hasAnyIssues), + showImportButton: parseBoolean(canImportIssues), + showLabel: !parseBoolean(hasAnyIssues), + // For IssuableByEmail component + emailsHelpPagePath, + initialEmail, + markdownHelpPath, + quickActionsHelpPath, + resetPath, + }, + render: (createComponent) => createComponent(IssuesListApp), + }); +} 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..be8deb3fe97 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql @@ -0,0 +1,90 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" +#import "./issue.fragment.graphql" + +query getIssues( + $isProject: Boolean = false + $isSignedIn: Boolean = false + $fullPath: ID! + $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 + 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( + 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/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql new file mode 100644 index 00000000000..1a345fd2877 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql @@ -0,0 +1,129 @@ +query getIssuesCount( + $isProject: Boolean = false + $fullPath: ID! + $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 + 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 + 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 + 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 + 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 + 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 + 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_list_details.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_list_details.query.graphql new file mode 100644 index 00000000000..a53dba8c7c8 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/get_issues_list_details.query.graphql @@ -0,0 +1,24 @@ +query getIssuesListDetails($fullPath: ID!) { + project(fullPath: $fullPath) { + id + issues { + nodes { + id + labels { + nodes { + id + title + color + } + } + } + } + jiraImportStatus + jiraImports { + nodes { + importedIssuesCount + jiraProjectKey + } + } + } +} diff --git a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql new file mode 100644 index 00000000000..07dae3fd756 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql @@ -0,0 +1,54 @@ +fragment IssueFragment on Issue { + id + iid + closedAt + confidential + createdAt + downvotes + dueDate + hidden + humanTimeEstimate + mergeRequestsCount + moved + title + updatedAt + upvotes + userDiscussionsCount @include(if: $isSignedIn) + webPath + 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/queries/label.fragment.graphql b/app/assets/javascripts/issues/list/queries/label.fragment.graphql new file mode 100644 index 00000000000..bb1d8f1ac9b --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/label.fragment.graphql @@ -0,0 +1,6 @@ +fragment Label on Label { + id + color + textColor + title +} diff --git a/app/assets/javascripts/issues/list/queries/milestone.fragment.graphql b/app/assets/javascripts/issues/list/queries/milestone.fragment.graphql new file mode 100644 index 00000000000..3cdf69bf585 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/milestone.fragment.graphql @@ -0,0 +1,4 @@ +fragment Milestone on Milestone { + id + title +} diff --git a/app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql b/app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql new file mode 100644 index 00000000000..160026a4742 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql @@ -0,0 +1,13 @@ +mutation reorderIssues( + $oldIndex: Int + $newIndex: Int + $namespace: String + $serializedVariables: String +) { + reorderIssues( + oldIndex: $oldIndex + newIndex: $newIndex + namespace: $namespace + serializedVariables: $serializedVariables + ) @client +} diff --git a/app/assets/javascripts/issues/list/queries/search_labels.query.graphql b/app/assets/javascripts/issues/list/queries/search_labels.query.graphql new file mode 100644 index 00000000000..44b57317161 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/search_labels.query.graphql @@ -0,0 +1,20 @@ +#import "./label.fragment.graphql" + +query searchLabels($fullPath: ID!, $search: String, $isProject: Boolean = false) { + group(fullPath: $fullPath) @skip(if: $isProject) { + id + labels(searchTerm: $search, includeAncestorGroups: true, includeDescendantGroups: true) { + nodes { + ...Label + } + } + } + project(fullPath: $fullPath) @include(if: $isProject) { + id + labels(searchTerm: $search, includeAncestorGroups: true) { + nodes { + ...Label + } + } + } +} diff --git a/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql b/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql new file mode 100644 index 00000000000..e7eb08104a6 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql @@ -0,0 +1,20 @@ +#import "./milestone.fragment.graphql" + +query searchMilestones($fullPath: ID!, $search: String, $isProject: Boolean = false) { + group(fullPath: $fullPath) @skip(if: $isProject) { + id + milestones(searchTitle: $search, includeAncestors: true, includeDescendants: true) { + nodes { + ...Milestone + } + } + } + project(fullPath: $fullPath) @include(if: $isProject) { + id + milestones(searchTitle: $search, includeAncestors: true) { + nodes { + ...Milestone + } + } + } +} diff --git a/app/assets/javascripts/issues/list/queries/search_projects.query.graphql b/app/assets/javascripts/issues/list/queries/search_projects.query.graphql new file mode 100644 index 00000000000..bd2f9bc2340 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/search_projects.query.graphql @@ -0,0 +1,14 @@ +query searchProjects($fullPath: ID!, $search: String) { + group(fullPath: $fullPath) { + id + projects(search: $search, includeSubgroups: true) { + nodes { + id + issuesEnabled + name + nameWithNamespace + webUrl + } + } + } +} diff --git a/app/assets/javascripts/issues/list/queries/search_users.query.graphql b/app/assets/javascripts/issues/list/queries/search_users.query.graphql new file mode 100644 index 00000000000..92517ad35d0 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/search_users.query.graphql @@ -0,0 +1,26 @@ +#import "./user.fragment.graphql" + +query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false) { + group(fullPath: $fullPath) @skip(if: $isProject) { + id + groupMembers(search: $search) { + nodes { + id + user { + ...User + } + } + } + } + project(fullPath: $fullPath) @include(if: $isProject) { + id + projectMembers(search: $search) { + nodes { + id + user { + ...User + } + } + } + } +} diff --git a/app/assets/javascripts/issues/list/queries/user.fragment.graphql b/app/assets/javascripts/issues/list/queries/user.fragment.graphql new file mode 100644 index 00000000000..3e5bc0f7b93 --- /dev/null +++ b/app/assets/javascripts/issues/list/queries/user.fragment.graphql @@ -0,0 +1,6 @@ +fragment User on User { + id + avatarUrl + name + username +} diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js new file mode 100644 index 00000000000..c64925edc28 --- /dev/null +++ b/app/assets/javascripts/issues/list/utils.js @@ -0,0 +1,261 @@ +import { + API_PARAM, + BLOCKING_ISSUES_ASC, + BLOCKING_ISSUES_DESC, + CREATED_ASC, + CREATED_DESC, + defaultPageSizeParams, + DUE_DATE_ASC, + DUE_DATE_DESC, + DUE_DATE_VALUES, + filters, + LABEL_PRIORITY_ASC, + LABEL_PRIORITY_DESC, + largePageSizeParams, + MILESTONE_DUE_ASC, + MILESTONE_DUE_DESC, + NORMAL_FILTER, + POPULARITY_ASC, + POPULARITY_DESC, + PRIORITY_ASC, + PRIORITY_DESC, + RELATIVE_POSITION_ASC, + SPECIAL_FILTER, + SPECIAL_FILTER_VALUES, + TITLE_ASC, + TITLE_DESC, + TOKEN_TYPE_ASSIGNEE, + TOKEN_TYPE_CONFIDENTIAL, + TOKEN_TYPE_ITERATION, + TOKEN_TYPE_MILESTONE, + TOKEN_TYPE_RELEASE, + TOKEN_TYPE_TYPE, + 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'; + +export const getInitialPageParams = (sortKey) => + sortKey === RELATIVE_POSITION_ASC ? largePageSizeParams : defaultPageSizeParams; + +export const getSortKey = (sort) => + Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort); + +export const getDueDateValue = (value) => (DUE_DATE_VALUES.includes(value) ? value : undefined); + +export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) => { + const sortOptions = [ + { + id: 1, + title: __('Priority'), + sortDirection: { + ascending: PRIORITY_ASC, + descending: PRIORITY_DESC, + }, + }, + { + id: 2, + title: __('Created date'), + sortDirection: { + ascending: CREATED_ASC, + descending: CREATED_DESC, + }, + }, + { + id: 3, + title: __('Last updated'), + sortDirection: { + ascending: UPDATED_ASC, + descending: UPDATED_DESC, + }, + }, + { + id: 4, + title: __('Milestone due date'), + sortDirection: { + ascending: MILESTONE_DUE_ASC, + descending: MILESTONE_DUE_DESC, + }, + }, + { + id: 5, + title: __('Due date'), + sortDirection: { + ascending: DUE_DATE_ASC, + descending: DUE_DATE_DESC, + }, + }, + { + id: 6, + title: __('Popularity'), + sortDirection: { + ascending: POPULARITY_ASC, + descending: POPULARITY_DESC, + }, + }, + { + id: 7, + title: __('Label priority'), + sortDirection: { + ascending: LABEL_PRIORITY_ASC, + descending: LABEL_PRIORITY_DESC, + }, + }, + { + id: 8, + title: __('Manual'), + sortDirection: { + ascending: RELATIVE_POSITION_ASC, + descending: RELATIVE_POSITION_ASC, + }, + }, + { + id: 9, + title: __('Title'), + sortDirection: { + ascending: TITLE_ASC, + descending: TITLE_DESC, + }, + }, + ]; + + if (hasIssueWeightsFeature) { + sortOptions.push({ + id: sortOptions.length + 1, + title: __('Weight'), + sortDirection: { + ascending: WEIGHT_ASC, + descending: WEIGHT_DESC, + }, + }); + } + + if (hasBlockedIssuesFeature) { + sortOptions.push({ + id: sortOptions.length + 1, + title: __('Blocking'), + sortDirection: { + ascending: BLOCKING_ISSUES_ASC, + descending: BLOCKING_ISSUES_DESC, + }, + }); + } + + return sortOptions; +}; + +const tokenTypes = Object.keys(filters); + +const getUrlParams = (tokenType) => + Object.values(filters[tokenType][URL_PARAM]).flatMap((filterObj) => Object.values(filterObj)); + +const urlParamKeys = tokenTypes.flatMap(getUrlParams); + +const getTokenTypeFromUrlParamKey = (urlParamKey) => + tokenTypes.find((tokenType) => getUrlParams(tokenType).includes(urlParamKey)); + +const getOperatorFromUrlParamKey = (tokenType, urlParamKey) => + Object.entries(filters[tokenType][URL_PARAM]).find(([, filterObj]) => + Object.values(filterObj).includes(urlParamKey), + )[0]; + +const convertToFilteredTokens = (locationSearch) => + Array.from(new URLSearchParams(locationSearch).entries()) + .filter(([key]) => urlParamKeys.includes(key)) + .map(([key, data]) => { + const type = getTokenTypeFromUrlParamKey(key); + const operator = getOperatorFromUrlParamKey(type, key); + return { + type, + value: { data, operator }, + }; + }); + +const convertToFilteredSearchTerms = (locationSearch) => + new URLSearchParams(locationSearch) + .get('search') + ?.split(' ') + .map((word) => ({ + type: FILTERED_SEARCH_TERM, + value: { + data: word, + }, + })) || []; + +export const getFilterTokens = (locationSearch) => { + if (!locationSearch) { + return []; + } + const filterTokens = convertToFilteredTokens(locationSearch); + const searchTokens = convertToFilteredSearchTerms(locationSearch); + return filterTokens.concat(searchTokens); +}; + +const getFilterType = (data, tokenType = '') => + SPECIAL_FILTER_VALUES.includes(data) || + (tokenType === TOKEN_TYPE_ASSIGNEE && isPositiveInteger(data)) + ? SPECIAL_FILTER + : NORMAL_FILTER; + +const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE]; + +const isWildcardValue = (tokenType, value) => + wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value); + +const requiresUpperCaseValue = (tokenType, value) => + tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value); + +const formatData = (token) => { + if (requiresUpperCaseValue(token.type, token.value.data)) { + return token.value.data.toUpperCase(); + } + if (token.type === TOKEN_TYPE_CONFIDENTIAL) { + return token.value.data === 'yes'; + } + return token.value.data; +}; + +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 = formatData(token); + 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][URL_PARAM][token.value.operator]?.[filterType]; + return Object.assign(acc, { + [param]: acc[param] ? [acc[param], token.value.data].flat() : token.value.data, + }); + }, {}); + +export const convertToSearchQuery = (filterTokens) => + filterTokens + .filter((token) => token.type === FILTERED_SEARCH_TERM && token.value.data) + .map((token) => token.value.data) + .join(' '); 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 deleted file mode 100644 index aece7372182..00000000000 --- a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue deleted file mode 100644 index b1a97a39869..00000000000 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ /dev/null @@ -1,821 +0,0 @@ - - - diff --git a/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue b/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue deleted file mode 100644 index fb1dbef666c..00000000000 --- a/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue b/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue deleted file mode 100644 index e749579af80..00000000000 --- a/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js deleted file mode 100644 index 4a380848b4f..00000000000 --- a/app/assets/javascripts/issues_list/constants.js +++ /dev/null @@ -1,316 +0,0 @@ -import { __, s__ } from '~/locale'; -import { - FILTER_ANY, - FILTER_CURRENT, - FILTER_NONE, - FILTER_STARTED, - FILTER_UPCOMING, - OPERATOR_IS, - OPERATOR_IS_NOT, -} from '~/vue_shared/components/filtered_search_bar/constants'; - -export const i18n = { - anonymousSearchingMessage: __('You must sign in to search for specific terms.'), - calendarLabel: __('Subscribe to calendar'), - closed: __('CLOSED'), - closedMoved: __('CLOSED (MOVED)'), - confidentialNo: __('No'), - confidentialYes: __('Yes'), - downvotes: __('Downvotes'), - editIssues: __('Edit issues'), - errorFetchingCounts: __('An error occurred while getting issue counts'), - errorFetchingIssues: __('An error occurred while loading issues'), - issueRepositioningMessage: __( - 'Issues are being rebalanced at the moment, so manual reordering is disabled.', - ), - jiraIntegrationMessage: s__( - 'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.', - ), - jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'), - jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'), - newIssueLabel: __('New issue'), - noClosedIssuesTitle: __('There are no closed issues'), - noOpenIssuesDescription: __('To keep this project going, create a new issue'), - noOpenIssuesTitle: __('There are no open issues'), - noIssuesSignedInDescription: __( - 'Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.', - ), - noIssuesSignedInTitle: __( - 'The Issue Tracker is the place to add things that need to be improved or solved in a project', - ), - noIssuesSignedOutButtonText: __('Register / Sign In'), - noIssuesSignedOutDescription: __( - 'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.', - ), - noIssuesSignedOutTitle: __('There are no issues to show'), - noSearchResultsDescription: __('To widen your search, change or remove filters above'), - noSearchResultsTitle: __('Sorry, your filter produced no results'), - relatedMergeRequests: __('Related merge requests'), - reorderError: __('An error occurred while reordering issues.'), - rssLabel: __('Subscribe to RSS feed'), - searchPlaceholder: __('Search or filter results...'), - upvotes: __('Upvotes'), -}; - -export const MAX_LIST_SIZE = 10; -export const PAGE_SIZE = 20; -export const PAGE_SIZE_MANUAL = 100; -export const PARAM_DUE_DATE = 'due_date'; -export const PARAM_SORT = 'sort'; -export const PARAM_STATE = 'state'; -export const RELATIVE_POSITION = 'relative_position'; - -export const defaultPageSizeParams = { - firstPageSize: PAGE_SIZE, -}; - -export const largePageSizeParams = { - firstPageSize: PAGE_SIZE_MANUAL, -}; - -export const DUE_DATE_NONE = '0'; -export const DUE_DATE_ANY = ''; -export const DUE_DATE_OVERDUE = 'overdue'; -export const DUE_DATE_WEEK = 'week'; -export const DUE_DATE_MONTH = 'month'; -export const DUE_DATE_NEXT_MONTH_AND_PREVIOUS_TWO_WEEKS = 'next_month_and_previous_two_weeks'; -export const DUE_DATE_VALUES = [ - DUE_DATE_NONE, - DUE_DATE_ANY, - DUE_DATE_OVERDUE, - DUE_DATE_WEEK, - DUE_DATE_MONTH, - DUE_DATE_NEXT_MONTH_AND_PREVIOUS_TWO_WEEKS, -]; - -export const BLOCKING_ISSUES_ASC = 'BLOCKING_ISSUES_ASC'; -export const BLOCKING_ISSUES_DESC = 'BLOCKING_ISSUES_DESC'; -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 TITLE_ASC = 'TITLE_ASC'; -export const TITLE_DESC = 'TITLE_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'; - -export const urlSortParams = { - [PRIORITY_ASC]: 'priority', - [PRIORITY_DESC]: 'priority_desc', - [CREATED_ASC]: 'created_asc', - [CREATED_DESC]: 'created_date', - [UPDATED_ASC]: 'updated_asc', - [UPDATED_DESC]: 'updated_desc', - [MILESTONE_DUE_ASC]: 'milestone', - [MILESTONE_DUE_DESC]: 'milestone_due_desc', - [DUE_DATE_ASC]: 'due_date', - [DUE_DATE_DESC]: 'due_date_desc', - [POPULARITY_ASC]: 'popularity_asc', - [POPULARITY_DESC]: 'popularity', - [LABEL_PRIORITY_ASC]: 'label_priority', - [LABEL_PRIORITY_DESC]: 'label_priority_desc', - [RELATIVE_POSITION_ASC]: RELATIVE_POSITION, - [WEIGHT_ASC]: 'weight', - [WEIGHT_DESC]: 'weight_desc', - [BLOCKING_ISSUES_ASC]: 'blocking_issues_asc', - [BLOCKING_ISSUES_DESC]: 'blocking_issues_desc', - [TITLE_ASC]: 'title_asc', - [TITLE_DESC]: 'title_desc', -}; - -export const API_PARAM = 'apiParam'; -export const URL_PARAM = 'urlParam'; -export const NORMAL_FILTER = 'normalFilter'; -export const SPECIAL_FILTER = 'specialFilter'; -export const ALTERNATIVE_FILTER = 'alternativeFilter'; -export const SPECIAL_FILTER_VALUES = [ - FILTER_NONE, - FILTER_ANY, - FILTER_CURRENT, - FILTER_UPCOMING, - FILTER_STARTED, -]; - -export const TOKEN_TYPE_AUTHOR = 'author_username'; -export const TOKEN_TYPE_ASSIGNEE = 'assignee_username'; -export const TOKEN_TYPE_MILESTONE = 'milestone'; -export const TOKEN_TYPE_LABEL = 'labels'; -export const TOKEN_TYPE_TYPE = 'type'; -export const TOKEN_TYPE_RELEASE = 'release'; -export const TOKEN_TYPE_MY_REACTION = 'my_reaction_emoji'; -export const TOKEN_TYPE_CONFIDENTIAL = 'confidential'; -export const TOKEN_TYPE_ITERATION = 'iteration'; -export const TOKEN_TYPE_EPIC = 'epic_id'; -export const TOKEN_TYPE_WEIGHT = 'weight'; - -export const filters = { - [TOKEN_TYPE_AUTHOR]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'authorUsername', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'author_username', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[author_username]', - }, - }, - }, - [TOKEN_TYPE_ASSIGNEE]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'assigneeUsernames', - [SPECIAL_FILTER]: 'assigneeId', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'assignee_username[]', - [SPECIAL_FILTER]: 'assignee_id', - [ALTERNATIVE_FILTER]: 'assignee_username', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[assignee_username][]', - }, - }, - }, - [TOKEN_TYPE_MILESTONE]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'milestoneTitle', - [SPECIAL_FILTER]: 'milestoneWildcardId', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'milestone_title', - [SPECIAL_FILTER]: 'milestone_title', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[milestone_title]', - }, - }, - }, - [TOKEN_TYPE_LABEL]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'labelName', - [SPECIAL_FILTER]: 'labelName', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'label_name[]', - [SPECIAL_FILTER]: 'label_name[]', - [ALTERNATIVE_FILTER]: 'label_name', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[label_name][]', - }, - }, - }, - [TOKEN_TYPE_TYPE]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'types', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'type[]', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[type][]', - }, - }, - }, - [TOKEN_TYPE_RELEASE]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'releaseTag', - [SPECIAL_FILTER]: 'releaseTagWildcardId', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'release_tag', - [SPECIAL_FILTER]: 'release_tag', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[release_tag]', - }, - }, - }, - [TOKEN_TYPE_MY_REACTION]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'myReactionEmoji', - [SPECIAL_FILTER]: 'myReactionEmoji', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'my_reaction_emoji', - [SPECIAL_FILTER]: 'my_reaction_emoji', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[my_reaction_emoji]', - }, - }, - }, - [TOKEN_TYPE_CONFIDENTIAL]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'confidential', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'confidential', - }, - }, - }, - [TOKEN_TYPE_ITERATION]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'iterationId', - [SPECIAL_FILTER]: 'iterationWildcardId', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'iteration_id', - [SPECIAL_FILTER]: 'iteration_id', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[iteration_id]', - }, - }, - }, - [TOKEN_TYPE_EPIC]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'epicId', - [SPECIAL_FILTER]: 'epicId', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'epic_id', - [SPECIAL_FILTER]: 'epic_id', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[epic_id]', - }, - }, - }, - [TOKEN_TYPE_WEIGHT]: { - [API_PARAM]: { - [NORMAL_FILTER]: 'weight', - [SPECIAL_FILTER]: 'weight', - }, - [URL_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'weight', - [SPECIAL_FILTER]: 'weight', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[weight]', - }, - }, - }, -}; diff --git a/app/assets/javascripts/issues_list/eventhub.js b/app/assets/javascripts/issues_list/eventhub.js deleted file mode 100644 index e31806ad199..00000000000 --- a/app/assets/javascripts/issues_list/eventhub.js +++ /dev/null @@ -1,3 +0,0 @@ -import createEventHub from '~/helpers/event_hub_factory'; - -export default createEventHub(); diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js deleted file mode 100644 index 30c227619e0..00000000000 --- a/app/assets/javascripts/issues_list/index.js +++ /dev/null @@ -1,165 +0,0 @@ -import produce from 'immer'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql'; -import IssuesListApp from 'ee_else_ce/issues_list/components/issues_list_app.vue'; -import createDefaultClient from '~/lib/graphql'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import JiraIssuesImportStatusRoot from './components/jira_issues_import_status_app.vue'; - -export function mountJiraIssuesListApp() { - const el = document.querySelector('.js-jira-issues-import-status'); - - if (!el) { - return false; - } - - const { issuesPath, projectPath } = el.dataset; - const canEdit = parseBoolean(el.dataset.canEdit); - const isJiraConfigured = parseBoolean(el.dataset.isJiraConfigured); - - if (!isJiraConfigured || !canEdit) { - return false; - } - - Vue.use(VueApollo); - const defaultClient = createDefaultClient(); - const apolloProvider = new VueApollo({ - defaultClient, - }); - - return new Vue({ - el, - apolloProvider, - render(createComponent) { - return createComponent(JiraIssuesImportStatusRoot, { - props: { - canEdit, - isJiraConfigured, - issuesPath, - projectPath, - }, - }); - }, - }); -} - -export function mountIssuesListApp() { - const el = document.querySelector('.js-issues-list'); - - if (!el) { - return false; - } - - Vue.use(VueApollo); - - const resolvers = { - Mutation: { - reorderIssues: (_, { oldIndex, newIndex, namespace, serializedVariables }, { cache }) => { - const variables = JSON.parse(serializedVariables); - const sourceData = cache.readQuery({ query: getIssuesQuery, variables }); - - const data = produce(sourceData, (draftData) => { - const issues = draftData[namespace].issues.nodes.slice(); - const issueToMove = issues[oldIndex]; - issues.splice(oldIndex, 1); - issues.splice(newIndex, 0, issueToMove); - - draftData[namespace].issues.nodes = issues; - }); - - cache.writeQuery({ query: getIssuesQuery, variables, data }); - }, - }, - }; - - const defaultClient = createDefaultClient(resolvers); - const apolloProvider = new VueApollo({ - defaultClient, - }); - - const { - autocompleteAwardEmojisPath, - calendarPath, - canBulkUpdate, - canEdit, - canImportIssues, - email, - emailsHelpPagePath, - emptyStateSvgPath, - exportCsvPath, - fullPath, - groupPath, - hasAnyIssues, - hasAnyProjects, - hasBlockedIssuesFeature, - hasIssuableHealthStatusFeature, - hasIssueWeightsFeature, - hasIterationsFeature, - hasMultipleIssueAssigneesFeature, - importCsvIssuesPath, - initialEmail, - isAnonymousSearchDisabled, - isIssueRepositioningDisabled, - isProject, - isSignedIn, - jiraIntegrationPath, - markdownHelpPath, - maxAttachmentSize, - newIssuePath, - projectImportJiraPath, - quickActionsHelpPath, - releasesPath, - resetPath, - rssPath, - showNewIssueLink, - signInPath, - } = el.dataset; - - return new Vue({ - el, - apolloProvider, - provide: { - autocompleteAwardEmojisPath, - calendarPath, - canBulkUpdate: parseBoolean(canBulkUpdate), - emptyStateSvgPath, - fullPath, - groupPath, - hasAnyIssues: parseBoolean(hasAnyIssues), - hasAnyProjects: parseBoolean(hasAnyProjects), - hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature), - hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature), - hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), - hasIterationsFeature: parseBoolean(hasIterationsFeature), - hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature), - isAnonymousSearchDisabled: parseBoolean(isAnonymousSearchDisabled), - isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled), - isProject: parseBoolean(isProject), - isSignedIn: parseBoolean(isSignedIn), - jiraIntegrationPath, - newIssuePath, - releasesPath, - rssPath, - showNewIssueLink: parseBoolean(showNewIssueLink), - signInPath, - // For CsvImportExportButtons component - canEdit: parseBoolean(canEdit), - email, - exportCsvPath, - importCsvIssuesPath, - maxAttachmentSize, - projectImportJiraPath, - showExportButton: parseBoolean(hasAnyIssues), - showImportButton: parseBoolean(canImportIssues), - showLabel: !parseBoolean(hasAnyIssues), - // For IssuableByEmail component - emailsHelpPagePath, - initialEmail, - markdownHelpPath, - quickActionsHelpPath, - resetPath, - }, - render: (createComponent) => createComponent(IssuesListApp), - }); -} 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 be8deb3fe97..00000000000 --- a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql +++ /dev/null @@ -1,90 +0,0 @@ -#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" -#import "./issue.fragment.graphql" - -query getIssues( - $isProject: Boolean = false - $isSignedIn: Boolean = false - $fullPath: ID! - $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 - 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( - 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/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql deleted file mode 100644 index 1a345fd2877..00000000000 --- a/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql +++ /dev/null @@ -1,129 +0,0 @@ -query getIssuesCount( - $isProject: Boolean = false - $fullPath: ID! - $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 - 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 - 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 - 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 - 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 - 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 - 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_list_details.query.graphql b/app/assets/javascripts/issues_list/queries/get_issues_list_details.query.graphql deleted file mode 100644 index a53dba8c7c8..00000000000 --- a/app/assets/javascripts/issues_list/queries/get_issues_list_details.query.graphql +++ /dev/null @@ -1,24 +0,0 @@ -query getIssuesListDetails($fullPath: ID!) { - project(fullPath: $fullPath) { - id - issues { - nodes { - id - labels { - nodes { - id - title - color - } - } - } - } - jiraImportStatus - jiraImports { - nodes { - importedIssuesCount - jiraProjectKey - } - } - } -} diff --git a/app/assets/javascripts/issues_list/queries/issue.fragment.graphql b/app/assets/javascripts/issues_list/queries/issue.fragment.graphql deleted file mode 100644 index 07dae3fd756..00000000000 --- a/app/assets/javascripts/issues_list/queries/issue.fragment.graphql +++ /dev/null @@ -1,54 +0,0 @@ -fragment IssueFragment on Issue { - id - iid - closedAt - confidential - createdAt - downvotes - dueDate - hidden - humanTimeEstimate - mergeRequestsCount - moved - title - updatedAt - upvotes - userDiscussionsCount @include(if: $isSignedIn) - webPath - 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/queries/label.fragment.graphql b/app/assets/javascripts/issues_list/queries/label.fragment.graphql deleted file mode 100644 index bb1d8f1ac9b..00000000000 --- a/app/assets/javascripts/issues_list/queries/label.fragment.graphql +++ /dev/null @@ -1,6 +0,0 @@ -fragment Label on Label { - id - color - textColor - title -} diff --git a/app/assets/javascripts/issues_list/queries/milestone.fragment.graphql b/app/assets/javascripts/issues_list/queries/milestone.fragment.graphql deleted file mode 100644 index 3cdf69bf585..00000000000 --- a/app/assets/javascripts/issues_list/queries/milestone.fragment.graphql +++ /dev/null @@ -1,4 +0,0 @@ -fragment Milestone on Milestone { - id - title -} diff --git a/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql b/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql deleted file mode 100644 index 160026a4742..00000000000 --- a/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql +++ /dev/null @@ -1,13 +0,0 @@ -mutation reorderIssues( - $oldIndex: Int - $newIndex: Int - $namespace: String - $serializedVariables: String -) { - reorderIssues( - oldIndex: $oldIndex - newIndex: $newIndex - namespace: $namespace - serializedVariables: $serializedVariables - ) @client -} diff --git a/app/assets/javascripts/issues_list/queries/search_labels.query.graphql b/app/assets/javascripts/issues_list/queries/search_labels.query.graphql deleted file mode 100644 index 44b57317161..00000000000 --- a/app/assets/javascripts/issues_list/queries/search_labels.query.graphql +++ /dev/null @@ -1,20 +0,0 @@ -#import "./label.fragment.graphql" - -query searchLabels($fullPath: ID!, $search: String, $isProject: Boolean = false) { - group(fullPath: $fullPath) @skip(if: $isProject) { - id - labels(searchTerm: $search, includeAncestorGroups: true, includeDescendantGroups: true) { - nodes { - ...Label - } - } - } - project(fullPath: $fullPath) @include(if: $isProject) { - id - labels(searchTerm: $search, includeAncestorGroups: true) { - nodes { - ...Label - } - } - } -} diff --git a/app/assets/javascripts/issues_list/queries/search_milestones.query.graphql b/app/assets/javascripts/issues_list/queries/search_milestones.query.graphql deleted file mode 100644 index e7eb08104a6..00000000000 --- a/app/assets/javascripts/issues_list/queries/search_milestones.query.graphql +++ /dev/null @@ -1,20 +0,0 @@ -#import "./milestone.fragment.graphql" - -query searchMilestones($fullPath: ID!, $search: String, $isProject: Boolean = false) { - group(fullPath: $fullPath) @skip(if: $isProject) { - id - milestones(searchTitle: $search, includeAncestors: true, includeDescendants: true) { - nodes { - ...Milestone - } - } - } - project(fullPath: $fullPath) @include(if: $isProject) { - id - milestones(searchTitle: $search, includeAncestors: true) { - nodes { - ...Milestone - } - } - } -} diff --git a/app/assets/javascripts/issues_list/queries/search_projects.query.graphql b/app/assets/javascripts/issues_list/queries/search_projects.query.graphql deleted file mode 100644 index bd2f9bc2340..00000000000 --- a/app/assets/javascripts/issues_list/queries/search_projects.query.graphql +++ /dev/null @@ -1,14 +0,0 @@ -query searchProjects($fullPath: ID!, $search: String) { - group(fullPath: $fullPath) { - id - projects(search: $search, includeSubgroups: true) { - nodes { - id - issuesEnabled - name - nameWithNamespace - webUrl - } - } - } -} diff --git a/app/assets/javascripts/issues_list/queries/search_users.query.graphql b/app/assets/javascripts/issues_list/queries/search_users.query.graphql deleted file mode 100644 index 92517ad35d0..00000000000 --- a/app/assets/javascripts/issues_list/queries/search_users.query.graphql +++ /dev/null @@ -1,26 +0,0 @@ -#import "./user.fragment.graphql" - -query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false) { - group(fullPath: $fullPath) @skip(if: $isProject) { - id - groupMembers(search: $search) { - nodes { - id - user { - ...User - } - } - } - } - project(fullPath: $fullPath) @include(if: $isProject) { - id - projectMembers(search: $search) { - nodes { - id - user { - ...User - } - } - } - } -} diff --git a/app/assets/javascripts/issues_list/queries/user.fragment.graphql b/app/assets/javascripts/issues_list/queries/user.fragment.graphql deleted file mode 100644 index 3e5bc0f7b93..00000000000 --- a/app/assets/javascripts/issues_list/queries/user.fragment.graphql +++ /dev/null @@ -1,6 +0,0 @@ -fragment User on User { - id - avatarUrl - name - username -} diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js deleted file mode 100644 index 99946e4e851..00000000000 --- a/app/assets/javascripts/issues_list/utils.js +++ /dev/null @@ -1,261 +0,0 @@ -import { - API_PARAM, - BLOCKING_ISSUES_ASC, - BLOCKING_ISSUES_DESC, - CREATED_ASC, - CREATED_DESC, - defaultPageSizeParams, - DUE_DATE_ASC, - DUE_DATE_DESC, - DUE_DATE_VALUES, - filters, - LABEL_PRIORITY_ASC, - LABEL_PRIORITY_DESC, - largePageSizeParams, - MILESTONE_DUE_ASC, - MILESTONE_DUE_DESC, - NORMAL_FILTER, - POPULARITY_ASC, - POPULARITY_DESC, - PRIORITY_ASC, - PRIORITY_DESC, - RELATIVE_POSITION_ASC, - SPECIAL_FILTER, - SPECIAL_FILTER_VALUES, - TITLE_ASC, - TITLE_DESC, - TOKEN_TYPE_ASSIGNEE, - TOKEN_TYPE_CONFIDENTIAL, - TOKEN_TYPE_ITERATION, - TOKEN_TYPE_MILESTONE, - TOKEN_TYPE_RELEASE, - TOKEN_TYPE_TYPE, - 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'; - -export const getInitialPageParams = (sortKey) => - sortKey === RELATIVE_POSITION_ASC ? largePageSizeParams : defaultPageSizeParams; - -export const getSortKey = (sort) => - Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort); - -export const getDueDateValue = (value) => (DUE_DATE_VALUES.includes(value) ? value : undefined); - -export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) => { - const sortOptions = [ - { - id: 1, - title: __('Priority'), - sortDirection: { - ascending: PRIORITY_ASC, - descending: PRIORITY_DESC, - }, - }, - { - id: 2, - title: __('Created date'), - sortDirection: { - ascending: CREATED_ASC, - descending: CREATED_DESC, - }, - }, - { - id: 3, - title: __('Last updated'), - sortDirection: { - ascending: UPDATED_ASC, - descending: UPDATED_DESC, - }, - }, - { - id: 4, - title: __('Milestone due date'), - sortDirection: { - ascending: MILESTONE_DUE_ASC, - descending: MILESTONE_DUE_DESC, - }, - }, - { - id: 5, - title: __('Due date'), - sortDirection: { - ascending: DUE_DATE_ASC, - descending: DUE_DATE_DESC, - }, - }, - { - id: 6, - title: __('Popularity'), - sortDirection: { - ascending: POPULARITY_ASC, - descending: POPULARITY_DESC, - }, - }, - { - id: 7, - title: __('Label priority'), - sortDirection: { - ascending: LABEL_PRIORITY_ASC, - descending: LABEL_PRIORITY_DESC, - }, - }, - { - id: 8, - title: __('Manual'), - sortDirection: { - ascending: RELATIVE_POSITION_ASC, - descending: RELATIVE_POSITION_ASC, - }, - }, - { - id: 9, - title: __('Title'), - sortDirection: { - ascending: TITLE_ASC, - descending: TITLE_DESC, - }, - }, - ]; - - if (hasIssueWeightsFeature) { - sortOptions.push({ - id: sortOptions.length + 1, - title: __('Weight'), - sortDirection: { - ascending: WEIGHT_ASC, - descending: WEIGHT_DESC, - }, - }); - } - - if (hasBlockedIssuesFeature) { - sortOptions.push({ - id: sortOptions.length + 1, - title: __('Blocking'), - sortDirection: { - ascending: BLOCKING_ISSUES_ASC, - descending: BLOCKING_ISSUES_DESC, - }, - }); - } - - return sortOptions; -}; - -const tokenTypes = Object.keys(filters); - -const getUrlParams = (tokenType) => - Object.values(filters[tokenType][URL_PARAM]).flatMap((filterObj) => Object.values(filterObj)); - -const urlParamKeys = tokenTypes.flatMap(getUrlParams); - -const getTokenTypeFromUrlParamKey = (urlParamKey) => - tokenTypes.find((tokenType) => getUrlParams(tokenType).includes(urlParamKey)); - -const getOperatorFromUrlParamKey = (tokenType, urlParamKey) => - Object.entries(filters[tokenType][URL_PARAM]).find(([, filterObj]) => - Object.values(filterObj).includes(urlParamKey), - )[0]; - -const convertToFilteredTokens = (locationSearch) => - Array.from(new URLSearchParams(locationSearch).entries()) - .filter(([key]) => urlParamKeys.includes(key)) - .map(([key, data]) => { - const type = getTokenTypeFromUrlParamKey(key); - const operator = getOperatorFromUrlParamKey(type, key); - return { - type, - value: { data, operator }, - }; - }); - -const convertToFilteredSearchTerms = (locationSearch) => - new URLSearchParams(locationSearch) - .get('search') - ?.split(' ') - .map((word) => ({ - type: FILTERED_SEARCH_TERM, - value: { - data: word, - }, - })) || []; - -export const getFilterTokens = (locationSearch) => { - if (!locationSearch) { - return []; - } - const filterTokens = convertToFilteredTokens(locationSearch); - const searchTokens = convertToFilteredSearchTerms(locationSearch); - return filterTokens.concat(searchTokens); -}; - -const getFilterType = (data, tokenType = '') => - SPECIAL_FILTER_VALUES.includes(data) || - (tokenType === TOKEN_TYPE_ASSIGNEE && isPositiveInteger(data)) - ? SPECIAL_FILTER - : NORMAL_FILTER; - -const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE]; - -const isWildcardValue = (tokenType, value) => - wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value); - -const requiresUpperCaseValue = (tokenType, value) => - tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value); - -const formatData = (token) => { - if (requiresUpperCaseValue(token.type, token.value.data)) { - return token.value.data.toUpperCase(); - } - if (token.type === TOKEN_TYPE_CONFIDENTIAL) { - return token.value.data === 'yes'; - } - return token.value.data; -}; - -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 = formatData(token); - 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][URL_PARAM][token.value.operator]?.[filterType]; - return Object.assign(acc, { - [param]: acc[param] ? [acc[param], token.value.data].flat() : token.value.data, - }); - }, {}); - -export const convertToSearchQuery = (filterTokens) => - filterTokens - .filter((token) => token.type === FILTERED_SEARCH_TERM && token.value.data) - .map((token) => token.value.data) - .join(' '); diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index b68e52d8ab1..725c38defc3 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -1,6 +1,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar'; -import { mountIssuesListApp } from '~/issues_list'; +import { mountIssuesListApp } from '~/issues/list'; import initManualOrdering from '~/issues/manual_ordering'; import { FILTERED_SEARCH } from '~/filtered_search/constants'; import initFilteredSearch from '~/pages/search/init_filtered_search'; diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index c8fdcfe0502..44b1d5277d1 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -2,7 +2,7 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import { initCsvImportExportButtons, initIssuableByEmail } from '~/issuable'; import { initBulkUpdateSidebar, initIssueStatusSelect } from '~/issuable/bulk_update_sidebar'; -import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues_list'; +import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues/list'; import initManualOrdering from '~/issues/manual_ordering'; import { FILTERED_SEARCH } from '~/filtered_search/constants'; import { ISSUABLE_INDEX } from '~/issuable/constants'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 677c50ed930..2e3a02b1712 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -1,5 +1,6 @@ @@ -48,28 +37,6 @@ export default {

{{ failedText }} -

diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue index 13b1e49f44e..071920856a8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue @@ -1,25 +1,22 @@