From 387c4b2c21a44360386a9b8ce6849e7f1b8a3de9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 4 May 2017 15:11:15 +0300 Subject: Backport of multiple_assignees_feature [ci skip] --- .../javascripts/blob/target_branch_dropdown.js | 4 +- app/assets/javascripts/blob/template_selector.js | 7 +- app/assets/javascripts/boards/boards_bundle.js | 2 +- .../boards/components/board_new_issue.js | 1 + .../javascripts/boards/components/board_sidebar.js | 61 +++- .../boards/components/issue_card_inner.js | 104 ++++-- .../boards/components/new_list_dropdown.js | 4 +- app/assets/javascripts/boards/models/assignee.js | 12 + app/assets/javascripts/boards/models/issue.js | 32 +- app/assets/javascripts/boards/models/user.js | 12 - app/assets/javascripts/gl_dropdown.js | 56 ++- app/assets/javascripts/issuable/issuable_bundle.js | 1 - .../time_tracking/components/no_tracking_pane.js | 12 - .../issuable/time_tracking/time_tracking_bundle.js | 66 ---- app/assets/javascripts/issue_status_select.js | 4 +- app/assets/javascripts/issues_bulk_assignment.js | 3 + app/assets/javascripts/labels_select.js | 7 +- app/assets/javascripts/main.js | 1 - app/assets/javascripts/members.js | 19 ++ app/assets/javascripts/milestone_select.js | 23 ++ app/assets/javascripts/namespace_select.js | 3 +- app/assets/javascripts/project.js | 3 +- .../protected_branch_access_dropdown.js | 5 +- .../protected_branch_dropdown.js | 3 +- .../sidebar/components/assignees/assignee_title.js | 41 +++ .../sidebar/components/assignees/assignees.js | 224 ++++++++++++ .../components/assignees/sidebar_assignees.js | 84 +++++ .../components/time_tracking/collapsed_state.js | 97 ++++++ .../components/time_tracking/comparison_pane.js | 98 ++++++ .../components/time_tracking/estimate_only_pane.js | 17 + .../sidebar/components/time_tracking/help_state.js | 44 +++ .../components/time_tracking/no_tracking_pane.js | 10 + .../time_tracking/sidebar_time_tracking.js | 45 +++ .../components/time_tracking/spent_only_pane.js | 15 + .../components/time_tracking/time_tracker.js | 163 +++++++++ app/assets/javascripts/sidebar/event_hub.js | 3 + .../sidebar/services/sidebar_service.js | 28 ++ app/assets/javascripts/sidebar/sidebar_bundle.js | 21 ++ app/assets/javascripts/sidebar/sidebar_mediator.js | 38 +++ .../javascripts/sidebar/stores/sidebar_store.js | 52 +++ app/assets/javascripts/subbable_resource.js | 51 --- app/assets/javascripts/subscription_select.js | 4 +- app/assets/javascripts/users_select.js | 377 ++++++++++++++++----- app/assets/stylesheets/framework/avatar.scss | 11 + app/assets/stylesheets/framework/dropdowns.scss | 12 +- app/assets/stylesheets/framework/lists.scss | 1 + app/assets/stylesheets/pages/boards.scss | 74 +++- app/assets/stylesheets/pages/diff.scss | 9 +- app/assets/stylesheets/pages/issuable.scss | 125 ++++++- app/controllers/concerns/issuable_actions.rb | 1 + app/controllers/concerns/issuable_collections.rb | 2 +- .../projects/boards/issues_controller.rb | 2 +- app/controllers/projects/issues_controller.rb | 6 +- app/finders/issuable_finder.rb | 2 +- app/finders/issues_finder.rb | 19 +- app/helpers/form_helper.rb | 33 ++ app/helpers/issuables_helper.rb | 10 + app/mailers/emails/issues.rb | 6 +- app/models/concerns/issuable.rb | 27 +- app/models/concerns/milestoneish.rb | 2 +- app/models/global_milestone.rb | 6 +- app/models/issue.rb | 35 +- app/models/issue_assignee.rb | 29 ++ app/models/merge_request.rb | 32 ++ app/models/milestone.rb | 5 +- app/models/user.rb | 5 + app/serializers/issuable_entity.rb | 1 - app/serializers/issue_entity.rb | 1 + app/serializers/merge_request_entity.rb | 1 + app/services/issuable/bulk_update_service.rb | 6 +- app/services/issuable_base_service.rb | 23 +- app/services/issues/base_service.rb | 19 ++ app/services/issues/update_service.rb | 14 +- .../merge_requests/assign_issues_service.rb | 4 +- app/services/merge_requests/base_service.rb | 5 + app/services/merge_requests/update_service.rb | 5 +- app/services/notification_recipient_service.rb | 7 +- app/services/notification_service.rb | 27 +- app/services/slash_commands/interpret_service.rb | 23 +- app/services/system_note_service.rb | 38 +++ app/services/todo_service.rb | 4 +- app/views/issues/_issue.atom.builder | 10 +- .../notify/_reassigned_issuable_email.text.erb | 6 - app/views/notify/new_issue_email.html.haml | 4 +- app/views/notify/new_issue_email.text.erb | 2 +- .../notify/new_mention_in_issue_email.text.erb | 2 +- app/views/notify/reassigned_issue_email.html.haml | 11 +- app/views/notify/reassigned_issue_email.text.erb | 7 +- .../reassigned_merge_request_email.html.haml | 10 +- .../notify/reassigned_merge_request_email.text.erb | 7 +- .../boards/components/sidebar/_assignee.html.haml | 48 ++- app/views/projects/issues/_issue.html.haml | 4 +- app/views/shared/issuable/_assignees.html.haml | 15 + app/views/shared/issuable/_participants.html.haml | 8 +- app/views/shared/issuable/_search_bar.html.haml | 9 + app/views/shared/issuable/_sidebar.html.haml | 95 ++++-- .../shared/issuable/form/_issue_assignee.html.haml | 30 ++ .../form/_merge_request_assignee.html.haml | 31 ++ app/views/shared/issuable/form/_metadata.html.haml | 25 +- app/views/shared/milestones/_issuable.html.haml | 8 +- .../unreleased/update-issue-board-cards-design.yml | 4 + config/webpack.config.js | 7 +- db/fixtures/development/09_issues.rb | 2 +- .../20170320171632_create_issue_assignees_table.rb | 40 +++ db/migrate/20170320173259_migrate_assignees.rb | 52 +++ db/schema.rb | 10 + doc/api/issues.md | 84 ++++- doc/user/project/integrations/webhooks.md | 12 + features/steps/dashboard/dashboard.rb | 2 +- features/steps/dashboard/todos.rb | 2 +- features/steps/group/milestones.rb | 4 +- features/steps/groups.rb | 4 +- lib/api/api.rb | 2 + lib/api/entities.rb | 6 +- lib/api/helpers/common_helpers.rb | 13 + lib/api/issues.rb | 9 +- lib/api/v3/entities.rb | 7 + lib/api/v3/issues.rb | 33 +- lib/api/v3/merge_requests.rb | 2 +- lib/api/v3/milestones.rb | 4 +- lib/banzai/reference_parser/issue_parser.rb | 2 +- lib/gitlab/chat_commands/presenters/issue_base.rb | 2 +- lib/gitlab/fogbugz_import/importer.rb | 18 +- lib/gitlab/github_import/issue_formatter.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 14 +- .../controllers/dashboard/todos_controller_spec.rb | 2 +- .../projects/boards/issues_controller_spec.rb | 2 +- .../controllers/projects/issues_controller_spec.rb | 8 +- .../projects/merge_requests_controller_spec.rb | 2 +- spec/features/atom/dashboard_issues_spec.rb | 8 +- spec/features/atom/issues_spec.rb | 6 +- spec/features/boards/boards_spec.rb | 2 +- spec/features/boards/modal_filter_spec.rb | 2 +- spec/features/boards/sidebar_spec.rb | 35 +- spec/features/dashboard/issuables_counter_spec.rb | 4 +- spec/features/dashboard/issues_spec.rb | 7 +- spec/features/dashboard_issues_spec.rb | 4 +- spec/features/gitlab_flavored_markdown_spec.rb | 4 +- spec/features/issues/award_emoji_spec.rb | 2 +- .../issues/filtered_search/filter_issues_spec.rb | 11 +- spec/features/issues/form_spec.rb | 54 ++- spec/features/issues/issue_sidebar_spec.rb | 15 + spec/features/issues/update_issues_spec.rb | 2 +- spec/features/issues_spec.rb | 40 ++- spec/features/merge_requests/assign_issues_spec.rb | 2 +- spec/features/milestones/show_spec.rb | 2 +- spec/features/projects/issuable_templates_spec.rb | 4 +- spec/features/search_spec.rb | 2 +- spec/features/unsubscribe_links_spec.rb | 2 +- spec/finders/issues_finder_spec.rb | 26 +- spec/fixtures/api/schemas/issue.json | 20 ++ .../fixtures/api/schemas/public_api/v4/issues.json | 17 +- spec/helpers/issuables_helper_spec.rb | 17 + spec/javascripts/boards/boards_store_spec.js | 19 +- spec/javascripts/boards/issue_card_spec.js | 110 ++++-- spec/javascripts/boards/issue_spec.js | 76 ++++- spec/javascripts/boards/list_spec.js | 19 +- spec/javascripts/boards/mock_data.js | 3 +- spec/javascripts/boards/modal_store_spec.js | 12 +- spec/javascripts/issuable_time_tracker_spec.js | 250 +++++++------- spec/javascripts/subbable_resource_spec.js | 63 ---- spec/lib/banzai/filter/redactor_filter_spec.rb | 2 +- .../gitlab/github_import/issue_formatter_spec.rb | 10 +- .../lib/gitlab/google_code_import/importer_spec.rb | 2 +- spec/lib/gitlab/import_export/all_models.yml | 3 +- .../import_export/project_tree_saver_spec.rb | 2 +- spec/lib/gitlab/project_search_results_spec.rb | 2 +- spec/lib/gitlab/search_results_spec.rb | 4 +- spec/mailers/notify_spec.rb | 10 +- spec/models/concerns/issuable_spec.rb | 109 +----- spec/models/concerns/milestoneish_spec.rb | 6 +- spec/models/event_spec.rb | 4 +- spec/models/issue_collection_spec.rb | 2 +- spec/models/issue_spec.rb | 91 ++++- spec/models/merge_request_spec.rb | 91 ++++- spec/requests/api/issues_spec.rb | 76 ++++- spec/requests/api/v3/issues_spec.rb | 72 +++- spec/services/issuable/bulk_update_service_spec.rb | 62 +++- spec/services/issues/close_service_spec.rb | 2 +- spec/services/issues/create_service_spec.rb | 86 ++++- spec/services/issues/update_service_spec.rb | 59 +++- .../merge_requests/assign_issues_service_spec.rb | 10 +- .../services/merge_requests/create_service_spec.rb | 82 ++++- .../services/merge_requests/update_service_spec.rb | 48 +++ spec/services/notes/slash_commands_service_spec.rb | 4 +- spec/services/notification_service_spec.rb | 80 ++--- .../services/projects/autocomplete_service_spec.rb | 2 +- .../slash_commands/interpret_service_spec.rb | 70 +++- spec/services/system_note_service_spec.rb | 45 +++ spec/services/todo_service_spec.rb | 26 +- .../issuable_slash_commands_shared_examples.rb | 4 +- spec/support/import_export/export_file_helper.rb | 2 +- .../issuable_create_service_shared_examples.rb | 52 --- ...reate_service_slash_commands_shared_examples.rb | 18 +- .../issuable_update_service_shared_examples.rb | 48 --- spec/support/time_tracking_shared_examples.rb | 10 +- 196 files changed, 3975 insertions(+), 1233 deletions(-) create mode 100644 app/assets/javascripts/boards/models/assignee.js delete mode 100644 app/assets/javascripts/boards/models/user.js delete mode 100644 app/assets/javascripts/issuable/issuable_bundle.js delete mode 100644 app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js delete mode 100644 app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js create mode 100644 app/assets/javascripts/sidebar/components/assignees/assignee_title.js create mode 100644 app/assets/javascripts/sidebar/components/assignees/assignees.js create mode 100644 app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/help_state.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.js create mode 100644 app/assets/javascripts/sidebar/components/time_tracking/time_tracker.js create mode 100644 app/assets/javascripts/sidebar/event_hub.js create mode 100644 app/assets/javascripts/sidebar/services/sidebar_service.js create mode 100644 app/assets/javascripts/sidebar/sidebar_bundle.js create mode 100644 app/assets/javascripts/sidebar/sidebar_mediator.js create mode 100644 app/assets/javascripts/sidebar/stores/sidebar_store.js delete mode 100644 app/assets/javascripts/subbable_resource.js create mode 100644 app/models/issue_assignee.rb delete mode 100644 app/views/notify/_reassigned_issuable_email.text.erb create mode 100644 app/views/shared/issuable/_assignees.html.haml create mode 100644 app/views/shared/issuable/form/_issue_assignee.html.haml create mode 100644 app/views/shared/issuable/form/_merge_request_assignee.html.haml create mode 100644 changelogs/unreleased/update-issue-board-cards-design.yml create mode 100644 db/migrate/20170320171632_create_issue_assignees_table.rb create mode 100644 db/migrate/20170320173259_migrate_assignees.rb create mode 100644 lib/api/helpers/common_helpers.rb delete mode 100644 spec/javascripts/subbable_resource_spec.js delete mode 100644 spec/support/services/issuable_create_service_shared_examples.rb diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js index 216f069ef71..d52d69b1274 100644 --- a/app/assets/javascripts/blob/target_branch_dropdown.js +++ b/app/assets/javascripts/blob/target_branch_dropdown.js @@ -37,8 +37,8 @@ class TargetBranchDropDown { } return SELECT_ITEM_MSG; }, - clicked(item, el, e) { - e.preventDefault(); + clicked(options) { + options.e.preventDefault(); self.onClick.call(self); }, fieldName: self.fieldName, diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index d7c1c32efbd..888883163c5 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -24,7 +24,7 @@ export default class TemplateSelector { search: { fields: ['name'], }, - clicked: (item, el, e) => this.fetchFileTemplate(item, el, e), + clicked: options => this.fetchFileTemplate(options), text: item => item.name, }); } @@ -51,7 +51,10 @@ export default class TemplateSelector { return this.$dropdownContainer.removeClass('hidden'); } - fetchFileTemplate(item, el, e) { + fetchFileTemplate(options) { + const { e } = options; + const item = options.selectedObj; + e.preventDefault(); return this.requestFile(item); } diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index b6dee8177d2..dbbfb6de3ea 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -11,7 +11,7 @@ require('./models/issue'); require('./models/label'); require('./models/list'); require('./models/milestone'); -require('./models/user'); +require('./models/assignee'); require('./stores/boards_store'); require('./stores/modal_store'); require('./services/board_service'); diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js index 0fa85b6fe14..1ce95b62138 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js +++ b/app/assets/javascripts/boards/components/board_new_issue.js @@ -26,6 +26,7 @@ export default { title: this.title, labels, subscribed: true, + assignees: [], }); this.list.newIssue(issue) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index f0066d4ec5d..317cef9f227 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -3,8 +3,13 @@ /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ +/* global Flash */ import Vue from 'vue'; +import eventHub from '../../sidebar/event_hub'; + +import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; +import Assignees from '../../sidebar/components/assignees/assignees'; require('./sidebar/remove_issue'); @@ -22,6 +27,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ detail: Store.detail, issue: {}, list: {}, + loadingAssignees: false, }; }, computed: { @@ -43,6 +49,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({ this.issue = this.detail.issue; this.list = this.detail.list; + + this.$nextTick(() => { + this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate; + }); }, deep: true }, @@ -53,12 +63,57 @@ gl.issueBoards.BoardSidebar = Vue.extend({ $('.right-sidebar').getNiceScroll().resize(); }); } - } + + this.issue = this.detail.issue; + this.list = this.detail.list; + }, + deep: true }, methods: { closeSidebar () { this.detail.issue = {}; - } + }, + assignSelf () { + // Notify gl dropdown that we are now assigning to current user + this.$refs.assigneeBlock.dispatchEvent(new Event('assignYourself')); + + this.addAssignee(this.currentUser); + this.saveAssignees(); + }, + removeAssignee (a) { + gl.issueBoards.BoardsStore.detail.issue.removeAssignee(a); + }, + addAssignee (a) { + gl.issueBoards.BoardsStore.detail.issue.addAssignee(a); + }, + removeAllAssignees () { + gl.issueBoards.BoardsStore.detail.issue.removeAllAssignees(); + }, + saveAssignees () { + this.loadingAssignees = true; + + gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint) + .then(() => { + this.loadingAssignees = false; + }) + .catch(() => { + this.loadingAssignees = false; + return new Flash('An error occurred while saving assignees'); + }); + }, + }, + created () { + // Get events from glDropdown + eventHub.$on('sidebar.removeAssignee', this.removeAssignee); + eventHub.$on('sidebar.addAssignee', this.addAssignee); + eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees); + eventHub.$on('sidebar.saveAssignees', this.saveAssignees); + }, + beforeDestroy() { + eventHub.$off('sidebar.removeAssignee', this.removeAssignee); + eventHub.$off('sidebar.addAssignee', this.addAssignee); + eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); + eventHub.$off('sidebar.saveAssignees', this.saveAssignees); }, mounted () { new IssuableContext(this.currentUser); @@ -70,5 +125,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }, components: { removeBtn: gl.issueBoards.RemoveIssueBtn, + 'assignee-title': AssigneeTitle, + assignees: Assignees, }, }); diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index fc154ee7b8b..2f06d186c50 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -31,18 +31,36 @@ gl.issueBoards.IssueCardInner = Vue.extend({ default: false, }, }, + data() { + return { + limitBeforeCounter: 3, + maxRender: 4, + maxCounter: 99, + }; + }, computed: { - cardUrl() { - return `${this.issueLinkBase}/${this.issue.id}`; + numberOverLimit() { + return this.issue.assignees.length - this.limitBeforeCounter; }, - assigneeUrl() { - return `${this.rootPath}${this.issue.assignee.username}`; + assigneeCounterTooltip() { + return `${this.assigneeCounterLabel} more`; + }, + assigneeCounterLabel() { + if (this.numberOverLimit > this.maxCounter) { + return `${this.maxCounter}+`; + } + + return `+${this.numberOverLimit}`; }, - assigneeUrlTitle() { - return `Assigned to ${this.issue.assignee.name}`; + shouldRenderCounter() { + if (this.issue.assignees.length <= this.maxRender) { + return false; + } + + return this.issue.assignees.length > this.numberOverLimit; }, - avatarUrlTitle() { - return `Avatar for ${this.issue.assignee.name}`; + cardUrl() { + return `${this.issueLinkBase}/${this.issue.id}`; }, issueId() { return `#${this.issue.id}`; @@ -52,6 +70,28 @@ gl.issueBoards.IssueCardInner = Vue.extend({ }, }, methods: { + isIndexLessThanlimit(index) { + return index < this.limitBeforeCounter; + }, + shouldRenderAssignee(index) { + // Eg. maxRender is 4, + // Render up to all 4 assignees if there are only 4 assigness + // Otherwise render up to the limitBeforeCounter + if (this.issue.assignees.length <= this.maxRender) { + return index < this.maxRender; + } + + return index < this.limitBeforeCounter; + }, + assigneeUrl(assignee) { + return `${this.rootPath}${assignee.username}`; + }, + assigneeUrlTitle(assignee) { + return `Assigned to ${assignee.name}`; + }, + avatarUrlTitle(assignee) { + return `Avatar for ${assignee.name}`; + }, showLabel(label) { if (!this.list) return true; @@ -105,25 +145,39 @@ gl.issueBoards.IssueCardInner = Vue.extend({ {{ issueId }} - - - +
+ + + + + {{ assigneeCounterLabel }} + +
-