diff options
35 files changed, 322 insertions, 273 deletions
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_sidebar.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_sidebar.vue index 554c7a573fe..1c9791bba78 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_sidebar.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_sidebar.vue @@ -64,6 +64,7 @@ export default { <sidebar-status :project-path="projectPath" :alert="alert" + :sidebar-collapsed="sidebarStatus" @toggle-sidebar="$emit('toggle-sidebar')" @alert-error="$emit('alert-error', $event)" /> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue index 2a999b908f9..2c138fefd6d 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue @@ -192,21 +192,34 @@ export default { </script> <template> - <div class="block alert-assignees"> - <div ref="assignees" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')"> - <gl-icon name="user" :size="14" /> - <gl-loading-icon v-if="isUpdating" /> - </div> - <gl-tooltip :target="() => $refs.assignees" boundary="viewport" placement="left"> - <gl-sprintf :message="$options.i18n.ASSIGNEES_BLOCK"> - <template #assignees> - {{ userName }} - </template> - </gl-sprintf> - </gl-tooltip> + <div + class="alert-assignees gl-py-5 gl-w-70p" + :class="{ 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100': !sidebarCollapsed }" + style="width: 70%" + > + <template v-if="sidebarCollapsed"> + <div + ref="assignees" + class="gl-mb-6 gl-ml-6" + data-testid="assignees-icon" + @click="$emit('toggle-sidebar')" + > + <gl-icon name="user" /> + <gl-loading-icon v-if="isUpdating" /> + </div> + <gl-tooltip :target="() => $refs.assignees" boundary="viewport" placement="left"> + <gl-sprintf :message="$options.i18n.ASSIGNEES_BLOCK"> + <template #assignees> + {{ userName }} + </template> + </gl-sprintf> + </gl-tooltip> + </template> - <div class="hide-collapsed"> - <p class="title gl-display-flex gl-justify-content-space-between"> + <div v-else> + <p + class="gl-text-gray-900 gl-mb-2 gl-line-height-20 gl-display-flex gl-justify-content-space-between" + > {{ __('Assignee') }} <a v-if="isEditable" diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue index fd40b5d9f65..832b154b312 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue @@ -25,7 +25,7 @@ export default { </script> <template> - <div class="block gl-display-flex gl-justify-content-space-between"> + <div class="block gl-display-flex gl-justify-content-space-between gl-border-b-gray-100!"> <span class="issuable-header-text hide-collapsed"> {{ __('To Do') }} </span> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue index 3822b9153a4..2a792dfa19a 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue @@ -30,6 +30,10 @@ export default { required: false, default: true, }, + sidebarCollapsed: { + type: Boolean, + required: false, + }, }, data() { return { @@ -61,21 +65,29 @@ export default { </script> <template> - <div class="block alert-status"> - <div ref="status" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')"> - <gl-icon name="status" :size="14" /> - <gl-loading-icon v-if="isUpdating" /> - </div> - <gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left"> - <gl-sprintf :message="s__('AlertManagement|Alert status: %{status}')"> - <template #status> - {{ alert.status.toLowerCase() }} - </template> - </gl-sprintf> - </gl-tooltip> + <div + class="alert-status gl-py-5 gl-w-70p" + :class="{ 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100': !sidebarCollapsed }" + style="width: 70%" + > + <template v-if="sidebarCollapsed"> + <div ref="status" class="gl-ml-6" data-testid="status-icon" @click="$emit('toggle-sidebar')"> + <gl-icon name="status" /> + <gl-loading-icon v-if="isUpdating" /> + </div> + <gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left"> + <gl-sprintf :message="s__('AlertManagement|Alert status: %{status}')"> + <template #status> + {{ alert.status.toLowerCase() }} + </template> + </gl-sprintf> + </gl-tooltip> + </template> - <div class="hide-collapsed"> - <p class="title gl-display-flex justify-content-between"> + <div v-else> + <p + class="gl-text-gray-900 gl-mb-2 gl-line-height-20 gl-display-flex gl-justify-content-space-between" + > {{ s__('AlertManagement|Status') }} <a v-if="isEditable" diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue index 271f0b4e4bb..a2a4046ab81 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue @@ -134,7 +134,12 @@ export default { </script> <template> - <div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }"> + <div + :class="{ + 'block todo': sidebarCollapsed, + 'gl-ml-auto': !sidebarCollapsed, + }" + > <todo data-testid="alert-todo-button" :collapsed="sidebarCollapsed" diff --git a/app/graphql/mutations/issues/common_mutation_arguments.rb b/app/graphql/mutations/issues/common_mutation_arguments.rb index 4b5b246281f..65768b85d14 100644 --- a/app/graphql/mutations/issues/common_mutation_arguments.rb +++ b/app/graphql/mutations/issues/common_mutation_arguments.rb @@ -22,6 +22,11 @@ module Mutations as: :discussion_locked, required: false, description: copy_field_description(Types::IssueType, :discussion_locked) + + argument :type, Types::IssueTypeEnum, + as: :issue_type, + required: false, + description: copy_field_description(Types::IssueType, :type) end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 45130dfc76d..24ed1db86ba 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -125,34 +125,12 @@ module ProjectsHelper project.fork_source if project.fork_source && can?(current_user, :read_project, project.fork_source) end - def project_nav_tabs - @nav_tabs ||= get_project_nav_tabs(@project, current_user) - end - def project_search_tabs?(tab) abilities = Array(search_tab_ability_map[tab]) abilities.any? { |ability| can?(current_user, ability, @project) } end - def project_nav_tab?(name) - project_nav_tabs.include? name - end - - def any_project_nav_tab?(tabs) - tabs.any? { |tab| project_nav_tab?(tab) } - end - - def project_for_deploy_key(deploy_key) - if deploy_key.has_access_to?(@project) - @project - else - deploy_key.projects.find do |project| - can?(current_user, :read_project, project) - end - end - end - def can_change_visibility_level?(project, current_user) can?(current_user, :change_visibility_level, project) end @@ -374,66 +352,6 @@ module ProjectsHelper private - # rubocop:disable Metrics/CyclomaticComplexity - def get_project_nav_tabs(project, current_user) - nav_tabs = [:home] - - unless project.empty_repo? - nav_tabs += [:files, :commits, :network, :graphs, :forks] if can?(current_user, :download_code, project) - nav_tabs << :releases if can?(current_user, :read_release, project) - end - - if project.repo_exists? && can?(current_user, :read_merge_request, project) - nav_tabs << :merge_requests - end - - if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) - nav_tabs << :container_registry - end - - if Feature.enabled?(:infrastructure_registry_page) - nav_tabs << :infrastructure_registry - end - - # Pipelines feature is tied to presence of builds - if can?(current_user, :read_build, project) - nav_tabs << :pipelines - end - - tab_ability_map.each do |tab, ability| - if can?(current_user, ability, project) - nav_tabs << tab - end - end - - apply_external_nav_tabs(nav_tabs, project) - - nav_tabs += package_nav_tabs(project, current_user) - - nav_tabs << :learn_gitlab if learn_gitlab_experiment_enabled?(project) - - nav_tabs - end - # rubocop:enable Metrics/CyclomaticComplexity - - def package_nav_tabs(project, current_user) - [].tap do |tabs| - if ::Gitlab.config.packages.enabled && can?(current_user, :read_package, project) - tabs << :packages - end - end - end - - def apply_external_nav_tabs(nav_tabs, project) - nav_tabs << :external_issue_tracker if project.external_issue_tracker - nav_tabs << :external_wiki if project.external_wiki - - if project.has_confluence? - nav_tabs.delete(:wiki) - nav_tabs << :confluence - end - end - def tab_ability_map { cycle_analytics: :read_cycle_analytics, diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 19a5b4a74bb..46aad221597 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -73,9 +73,9 @@ class IssueTrackerService < Service def fields [ - { type: 'text', name: 'project_url', title: _('Project URL'), required: true }, - { type: 'text', name: 'issues_url', title: s_('ProjectService|Issue URL'), required: true }, - { type: 'text', name: 'new_issue_url', title: s_('ProjectService|New issue URL'), required: true } + { type: 'text', name: 'project_url', title: _('Project URL'), help: s_('IssueTracker|The URL to the project in the external issue tracker.'), required: true }, + { type: 'text', name: 'issues_url', title: s_('IssueTracker|Issue URL'), help: s_('IssueTracker|The URL to view an issue in the external issue tracker. Must contain %{colon_id}.') % { colon_id: '<code>:id</code>'.html_safe }, required: true }, + { type: 'text', name: 'new_issue_url', title: s_('IssueTracker|New issue URL'), help: s_('IssueTracker|The URL to create an issue in the external issue tracker.'), required: true } ] end diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb index 94cbd6c5959..9760a22a872 100644 --- a/app/models/project_services/youtrack_service.rb +++ b/app/models/project_services/youtrack_service.rb @@ -33,8 +33,8 @@ class YoutrackService < IssueTrackerService def fields [ - { type: 'text', name: 'project_url', title: _('Project URL'), required: true }, - { type: 'text', name: 'issues_url', title: s_('ProjectService|Issue URL'), required: true } + { type: 'text', name: 'project_url', title: _('Project URL'), help: s_('IssueTracker|The URL to the project in YouTrack.'), required: true }, + { type: 'text', name: 'issues_url', title: s_('ProjectService|Issue URL'), help: s_('IssueTracker|The URL to view an issue in the YouTrack project. Must contain %{colon_id}.') % { colon_id: '<code>:id</code>'.html_safe }, required: true } ] end end diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 3d0c6baffd5..a06f9f8d6ef 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -1,3 +1 @@ --# We're migration the project sidebar to a logical model based structure. If you need to update --# any of the existing menus, you can find them in app/views/layouts/nav/sidebar/_project_menus.html.haml. = render partial: 'shared/nav/sidebar', object: Sidebars::Projects::Panel.new(project_sidebar_context(@project, current_user, current_ref)) diff --git a/changelogs/unreleased/330470-default-enable-sync_traversal_ids.yml b/changelogs/unreleased/330470-default-enable-sync_traversal_ids.yml new file mode 100644 index 00000000000..67b05da0b9d --- /dev/null +++ b/changelogs/unreleased/330470-default-enable-sync_traversal_ids.yml @@ -0,0 +1,5 @@ +--- +title: Sync traversal path of namespaces +merge_request: 61329 +author: +type: performance diff --git a/changelogs/unreleased/msj-ui-trackers-help.yml b/changelogs/unreleased/msj-ui-trackers-help.yml new file mode 100644 index 00000000000..a02502b2321 --- /dev/null +++ b/changelogs/unreleased/msj-ui-trackers-help.yml @@ -0,0 +1,5 @@ +--- +title: Add issue tracker integrations help text +merge_request: 61158 +author: +type: other diff --git a/changelogs/unreleased/sy-create-incidents-via-graphql.yml b/changelogs/unreleased/sy-create-incidents-via-graphql.yml new file mode 100644 index 00000000000..4c68c526774 --- /dev/null +++ b/changelogs/unreleased/sy-create-incidents-via-graphql.yml @@ -0,0 +1,5 @@ +--- +title: Add support for creating/modifying different issue types via GraphQL API +merge_request: 60747 +author: +type: added diff --git a/config/feature_flags/development/sync_traversal_ids.yml b/config/feature_flags/development/sync_traversal_ids.yml index 52777c502e6..bd612f9646c 100644 --- a/config/feature_flags/development/sync_traversal_ids.yml +++ b/config/feature_flags/development/sync_traversal_ids.yml @@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52854 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321947 group: group::access type: development -default_enabled: false +default_enabled: true diff --git a/config/metrics/counts_28d/20210216181139_issues.yml b/config/metrics/counts_28d/20210216181139_issues.yml index 46494caaff7..04734857bdd 100644 --- a/config/metrics/counts_28d/20210216181139_issues.yml +++ b/config/metrics/counts_28d/20210216181139_issues.yml @@ -1,18 +1,19 @@ --- key_path: usage_activity_by_stage_monthly.plan.issues -description: Count of MAU creating issues +description: Count of users creating Issues in last 28 days. product_section: dev -product_stage: plan +product_stage: plan product_group: group::project management -product_category: issue_tracking +product_category: issue_tracking value_type: number status: data_available time_frame: 28d data_source: database +instrumentation_class: 'Gitlab::Usage::Metrics::Instrumentations::CountUsersCreatingIssuesMetric' distribution: - ce -- ee +- ee tier: - free -- premium +- premium - ultimate diff --git a/config/metrics/counts_all/20210216181102_issues.yml b/config/metrics/counts_all/20210216181102_issues.yml index ca89db705f1..c4426915d02 100644 --- a/config/metrics/counts_all/20210216181102_issues.yml +++ b/config/metrics/counts_all/20210216181102_issues.yml @@ -9,6 +9,7 @@ value_type: number status: data_available time_frame: all data_source: database +instrumentation_class: 'Gitlab::Usage::Metrics::Instrumentations::CountIssuesMetric' distribution: - ce - ee diff --git a/config/metrics/counts_all/20210216181115_issues.yml b/config/metrics/counts_all/20210216181115_issues.yml index 0c4db95b275..d3c7fc4b79b 100644 --- a/config/metrics/counts_all/20210216181115_issues.yml +++ b/config/metrics/counts_all/20210216181115_issues.yml @@ -9,6 +9,7 @@ value_type: number status: data_available time_frame: all data_source: database +instrumentation_class: 'Gitlab::Usage::Metrics::Instrumentations::CountUsersCreatingIssuesMetric' distribution: - ce - ee diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md index 783030a9220..8a851afe35b 100644 --- a/doc/administration/monitoring/prometheus/postgres_exporter.md +++ b/doc/administration/monitoring/prometheus/postgres_exporter.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # PostgreSQL Server Exporter **(FREE SELF)** -The [PostgreSQL Server Exporter](https://github.com/wrouesnel/postgres_exporter) allows you to export various PostgreSQL metrics. +The [PostgreSQL Server Exporter](https://github.com/prometheus-community/postgres_exporter) allows you to export various PostgreSQL metrics. For installations from source you must install and configure it yourself. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 4a2a7c0de75..8628f956b99 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1188,6 +1188,7 @@ Input type: `CreateIssueInput` | <a id="mutationcreateissuemilestoneid"></a>`milestoneId` | [`MilestoneID`](#milestoneid) | The ID of the milestone to assign to the issue. On update milestone will be removed if set to null. | | <a id="mutationcreateissueprojectpath"></a>`projectPath` | [`ID!`](#id) | Project full path the issue is associated with. | | <a id="mutationcreateissuetitle"></a>`title` | [`String!`](#string) | Title of the issue. | +| <a id="mutationcreateissuetype"></a>`type` | [`IssueType`](#issuetype) | Type of the issue. | | <a id="mutationcreateissueweight"></a>`weight` | [`Int`](#int) | The weight of the issue. | #### Fields @@ -3893,6 +3894,7 @@ Input type: `UpdateIssueInput` | <a id="mutationupdateissueremovelabelids"></a>`removeLabelIds` | [`[ID!]`](#id) | The IDs of labels to be removed from the issue. | | <a id="mutationupdateissuestateevent"></a>`stateEvent` | [`IssueStateEvent`](#issuestateevent) | Close or reopen an issue. | | <a id="mutationupdateissuetitle"></a>`title` | [`String`](#string) | Title of the issue. | +| <a id="mutationupdateissuetype"></a>`type` | [`IssueType`](#issuetype) | Type of the issue. | | <a id="mutationupdateissueweight"></a>`weight` | [`Int`](#int) | The weight of the issue. | #### Fields diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 8cee670f72a..f64dd1c7d4d 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -18256,7 +18256,7 @@ Tiers: `free` ### `usage_activity_by_stage_monthly.plan.issues` -Count of MAU creating issues +Count of users creating Issues in last 28 days. [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216181139_issues.yml) diff --git a/lib/gitlab/usage/metrics/instrumentations/count_issues_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_issues_metric.rb new file mode 100644 index 00000000000..34247f4f6dd --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/count_issues_metric.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class CountIssuesMetric < DatabaseMetric + operation :count + + start { Issue.minimum(:id) } + finish { Issue.maximum(:id) } + + relation { Issue } + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric.rb new file mode 100644 index 00000000000..c8331ce5b31 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class CountUsersCreatingIssuesMetric < DatabaseMetric + operation :distinct_count, column: :author_id + + relation { Issue } + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c479217861d..211376dac02 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18195,6 +18195,27 @@ msgstr "" msgid "IssueTracker|Custom issue tracker" msgstr "" +msgid "IssueTracker|Issue URL" +msgstr "" + +msgid "IssueTracker|New issue URL" +msgstr "" + +msgid "IssueTracker|The URL to create an issue in the external issue tracker." +msgstr "" + +msgid "IssueTracker|The URL to the project in YouTrack." +msgstr "" + +msgid "IssueTracker|The URL to the project in the external issue tracker." +msgstr "" + +msgid "IssueTracker|The URL to view an issue in the YouTrack project. Must contain %{colon_id}." +msgstr "" + +msgid "IssueTracker|The URL to view an issue in the external issue tracker. Must contain %{colon_id}." +msgstr "" + msgid "IssueTracker|Use Bugzilla as this project's issue tracker." msgstr "" @@ -25366,9 +25387,6 @@ msgstr "" msgid "ProjectService|Must have permission to trigger a manual build in TeamCity." msgstr "" -msgid "ProjectService|New issue URL" -msgstr "" - msgid "ProjectService|Perform common operations on GitLab project: %{project_name}" msgstr "" diff --git a/package.json b/package.json index 5463bd0c63c..47dce38a769 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@rails/actioncable": "^6.0.3-4", "@rails/ujs": "^6.0.3-4", "@sentry/browser": "^5.22.3", - "@sourcegraph/code-host-integration": "0.0.52", + "@sourcegraph/code-host-integration": "0.0.57", "@tiptap/core": "^2.0.0-beta.38", "@tiptap/extension-blockquote": "^2.0.0-beta.6", "@tiptap/extension-bold": "^2.0.0-beta.6", @@ -101,7 +101,7 @@ "codesandbox-api": "0.0.23", "compression-webpack-plugin": "^5.0.2", "copy-webpack-plugin": "^6.4.1", - "core-js": "^3.12.0", + "core-js": "^3.12.1", "cron-validator": "^1.1.1", "cropper": "^2.3.0", "css-loader": "^2.1.1", diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js index 28646994ed1..db9b0930c06 100644 --- a/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js @@ -1,7 +1,7 @@ import { GlDropdownItem } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue'; import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue'; import AlertSetAssignees from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql'; @@ -13,6 +13,29 @@ describe('Alert Details Sidebar Assignees', () => { let wrapper; let mock; + const mockPath = '/-/autocomplete/users.json'; + const mockUsers = [ + { + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: 1, + name: 'User 1', + username: 'root', + }, + { + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: 2, + name: 'User 2', + username: 'not-root', + }, + ]; + + const findAssigned = () => wrapper.findByTestId('assigned-users'); + const findDropdown = () => wrapper.findComponent(GlDropdownItem); + const findSidebarIcon = () => wrapper.findByTestId('assignees-icon'); + const findUnassigned = () => wrapper.findByTestId('unassigned-users'); + function mountComponent({ data, users = [], @@ -21,7 +44,7 @@ describe('Alert Details Sidebar Assignees', () => { loading = false, stubs = {}, } = {}) { - wrapper = shallowMount(SidebarAssignees, { + wrapper = shallowMountExtended(SidebarAssignees, { data() { return { users, @@ -56,10 +79,7 @@ describe('Alert Details Sidebar Assignees', () => { mock.restore(); }); - const findAssigned = () => wrapper.find('[data-testid="assigned-users"]'); - const findUnassigned = () => wrapper.find('[data-testid="unassigned-users"]'); - - describe('updating the alert status', () => { + describe('sidebar expanded', () => { const mockUpdatedMutationResult = { data: { alertSetAssignees: { @@ -73,30 +93,13 @@ describe('Alert Details Sidebar Assignees', () => { beforeEach(() => { mock = new MockAdapter(axios); - const path = '/-/autocomplete/users.json'; - const users = [ - { - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - id: 1, - name: 'User 1', - username: 'root', - }, - { - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - id: 2, - name: 'User 2', - username: 'not-root', - }, - ]; - mock.onGet(path).replyOnce(200, users); + mock.onGet(mockPath).replyOnce(200, mockUsers); mountComponent({ data: { alert: mockAlert }, sidebarCollapsed: false, loading: false, - users, + users: mockUsers, stubs: { SidebarAssignee, }, @@ -106,7 +109,11 @@ describe('Alert Details Sidebar Assignees', () => { it('renders a unassigned option', async () => { wrapper.setData({ isDropdownSearching: false }); await wrapper.vm.$nextTick(); - expect(wrapper.find(GlDropdownItem).text()).toBe('Unassigned'); + expect(findDropdown().text()).toBe('Unassigned'); + }); + + it('does not display the collapsed sidebar icon', () => { + expect(findSidebarIcon().exists()).toBe(false); }); it('calls `$apollo.mutate` with `AlertSetAssignees` mutation and variables containing `iid`, `assigneeUsernames`, & `projectPath`', async () => { @@ -170,4 +177,28 @@ describe('Alert Details Sidebar Assignees', () => { expect(findAssigned().find('.dropdown-menu-user-username').text()).toBe('@root'); }); }); + + describe('sidebar collapsed', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + + mock.onGet(mockPath).replyOnce(200, mockUsers); + + mountComponent({ + data: { alert: mockAlert }, + loading: false, + users: mockUsers, + stubs: { + SidebarAssignee, + }, + }); + }); + it('does not display the status dropdown', () => { + expect(findDropdown().exists()).toBe(false); + }); + + it('does display the collapsed sidebar icon', () => { + expect(findSidebarIcon().exists()).toBe(true); + }); + }); }); diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js index 0014957517f..1b069c1fe90 100644 --- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js @@ -1,5 +1,5 @@ import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql'; import AlertStatus from '~/vue_shared/alert_details/components/alert_status.vue'; import AlertSidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue'; @@ -13,9 +13,10 @@ describe('Alert Details Sidebar Status', () => { const findStatusDropdown = () => wrapper.find(GlDropdown); const findStatusDropdownItem = () => wrapper.find(GlDropdownItem); const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findStatusDropdownHeader = () => wrapper.find('[data-testid="dropdown-header"]'); + const findStatusDropdownHeader = () => wrapper.findByTestId('dropdown-header'); const findAlertStatus = () => wrapper.findComponent(AlertStatus); - const findStatus = () => wrapper.find('[data-testid="status"]'); + const findStatus = () => wrapper.findByTestId('status'); + const findSidebarIcon = () => wrapper.findByTestId('status-icon'); function mountComponent({ data, @@ -24,7 +25,7 @@ describe('Alert Details Sidebar Status', () => { stubs = {}, provide = {}, } = {}) { - wrapper = mount(AlertSidebarStatus, { + wrapper = mountExtended(AlertSidebarStatus, { propsData: { alert: { ...mockAlert }, ...data, @@ -52,7 +53,7 @@ describe('Alert Details Sidebar Status', () => { } }); - describe('Alert Sidebar Dropdown Status', () => { + describe('sidebar expanded', () => { beforeEach(() => { mountComponent({ data: { alert: mockAlert }, @@ -69,6 +70,10 @@ describe('Alert Details Sidebar Status', () => { expect(findStatusDropdownHeader().exists()).toBe(true); }); + it('does not display the collapsed sidebar icon', () => { + expect(findSidebarIcon().exists()).toBe(false); + }); + describe('updating the alert status', () => { const mockUpdatedMutationResult = { data: { @@ -109,22 +114,40 @@ describe('Alert Details Sidebar Status', () => { expect(findStatusLoadingIcon().exists()).toBe(false); expect(findStatus().text()).toBe('Triggered'); }); + + it('renders default translated statuses', () => { + mountComponent({ sidebarCollapsed: false }); + expect(findAlertStatus().props('statuses')).toBe(PAGE_CONFIG.OPERATIONS.STATUSES); + expect(findStatus().text()).toBe('Triggered'); + }); + + it('renders translated statuses', () => { + const status = 'TEST'; + const statuses = { [status]: 'Test' }; + mountComponent({ + data: { alert: { ...mockAlert, status } }, + provide: { statuses }, + sidebarCollapsed: false, + }); + expect(findAlertStatus().props('statuses')).toBe(statuses); + expect(findStatus().text()).toBe(statuses.TEST); + }); }); }); - describe('Statuses', () => { - it('renders default translated statuses', () => { - mountComponent({}); - expect(findAlertStatus().props('statuses')).toBe(PAGE_CONFIG.OPERATIONS.STATUSES); - expect(findStatus().text()).toBe('Triggered'); + describe('sidebar collapsed', () => { + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + loading: false, + }); + }); + it('does not display the status dropdown', () => { + expect(findStatusDropdown().exists()).toBe(false); }); - it('renders translated statuses', () => { - const status = 'TEST'; - const statuses = { [status]: 'Test' }; - mountComponent({ data: { alert: { ...mockAlert, status } }, provide: { statuses } }); - expect(findAlertStatus().props('statuses')).toBe(statuses); - expect(findStatus().text()).toBe(statuses.TEST); + it('does display the collapsed sidebar icon', () => { + expect(findSidebarIcon().exists()).toBe(true); }); }); }); diff --git a/spec/graphql/mutations/issues/create_spec.rb b/spec/graphql/mutations/issues/create_spec.rb index 422ad40a9cb..b32f0991959 100644 --- a/spec/graphql/mutations/issues/create_spec.rb +++ b/spec/graphql/mutations/issues/create_spec.rb @@ -19,7 +19,8 @@ RSpec.describe Mutations::Issues::Create do description: 'new description', confidential: true, due_date: Date.tomorrow, - discussion_locked: true + discussion_locked: true, + issue_type: 'issue' } end @@ -93,6 +94,16 @@ RSpec.describe Mutations::Issues::Create do expect(mutated_issue.iid).not_to eq(special_params[:iid]) end end + + context 'when creating a non-default issue type' do + before do + mutation_params[:issue_type] = 'incident' + end + + it 'creates issue with correct values' do + expect(mutated_issue.issue_type).to eq('incident') + end + end end context 'when creating an issue as owner' do diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb index f10e257e153..bd780477658 100644 --- a/spec/graphql/mutations/issues/update_spec.rb +++ b/spec/graphql/mutations/issues/update_spec.rb @@ -128,6 +128,14 @@ RSpec.describe Mutations::Issues::Update do expect(issue.reload.labels).to match_array([project_label, label_2]) end end + + context 'when changing type' do + it 'changes the type of the issue' do + mutation_params[:issue_type] = 'incident' + + expect { subject }.to change { issue.reload.issue_type }.from('issue').to('incident') + end + end end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index fe7aa9894e9..1804a9a99cf 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -390,93 +390,6 @@ RSpec.describe ProjectsHelper do end end - describe '#get_project_nav_tabs' do - before do - allow(helper).to receive(:current_user).and_return(user) - allow(helper).to receive(:can?) { true } - end - - subject do - helper.send(:get_project_nav_tabs, project, user) - end - - context 'when builds feature is enabled' do - before do - allow(project).to receive(:builds_enabled?).and_return(true) - end - - it "does include pipelines tab" do - is_expected.to include(:pipelines) - end - end - - context 'when builds feature is disabled' do - before do - allow(project).to receive(:builds_enabled?).and_return(false) - end - - context 'when user has access to builds' do - it "does include pipelines tab" do - is_expected.to include(:pipelines) - end - end - - context 'when user does not have access to builds' do - before do - allow(helper).to receive(:can?) { false } - end - - it "does not include pipelines tab" do - is_expected.not_to include(:pipelines) - end - end - end - - context 'when project has external wiki' do - it 'includes external wiki tab' do - project.create_external_wiki_service(active: true, properties: { 'external_wiki_url' => 'https://gitlab.com' }) - project.reload - - is_expected.to include(:external_wiki) - end - end - - context 'when project does not have external wiki' do - it 'does not include external wiki tab' do - expect(project.external_wiki).to be_nil - is_expected.not_to include(:external_wiki) - end - end - - context 'when project has confluence enabled' do - before do - allow(project).to receive(:has_confluence?).and_return(true) - end - - it { is_expected.to include(:confluence) } - it { is_expected.not_to include(:wiki) } - end - - context 'when project does not have confluence enabled' do - it { is_expected.not_to include(:confluence) } - it { is_expected.to include(:wiki) } - end - - context 'learn gitlab experiment' do - context 'when it is enabled' do - before do - expect(helper).to receive(:learn_gitlab_experiment_enabled?).with(project).and_return(true) - end - - it { is_expected.to include(:learn_gitlab) } - end - - context 'when it is not enabled' do - it { is_expected.not_to include(:learn_gitlab) } - end - end - end - describe '#show_projects' do let(:projects) do Project.all diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_metric_spec.rb new file mode 100644 index 00000000000..c3b59904f41 --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_metric_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountIssuesMetric do + let_it_be(:issue) { create(:issue) } + + it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }, 1 +end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb new file mode 100644 index 00000000000..9f4686ab6cd --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountUsersCreatingIssuesMetric do + let_it_be(:author) { create(:user) } + let_it_be(:issues) { create_list(:issue, 2, author: author, created_at: 4.days.ago) } + let_it_be(:old_issue) { create(:issue, author: author, created_at: 2.months.ago) } + + context 'with all time frame' do + it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }, 1 + end + + context 'for 28d time frame' do + it_behaves_like 'a correct instrumented metric value', { time_frame: '28d', data_source: 'database' }, 1 + end +end diff --git a/spec/lib/gitlab/usage_data_metrics_spec.rb b/spec/lib/gitlab/usage_data_metrics_spec.rb index 2692e70cabf..18acd767c6d 100644 --- a/spec/lib/gitlab/usage_data_metrics_spec.rb +++ b/spec/lib/gitlab/usage_data_metrics_spec.rb @@ -30,6 +30,18 @@ RSpec.describe Gitlab::UsageDataMetrics do expect(subject[:redis_hll_counters][:quickactions]).to include(:i_quickactions_approve_monthly) expect(subject[:redis_hll_counters][:quickactions]).to include(:i_quickactions_approve_weekly) end + + it 'includes counts keys' do + expect(subject[:counts]).to include(:issues) + end + + it 'includes usage_activity_by_stage keys' do + expect(subject[:usage_activity_by_stage][:plan]).to include(:issues) + end + + it 'includes usage_activity_by_stage_monthly keys' do + expect(subject[:usage_activity_by_stage_monthly][:plan]).to include(:issues) + end end end end diff --git a/spec/requests/api/graphql/mutations/issues/create_spec.rb b/spec/requests/api/graphql/mutations/issues/create_spec.rb index 39b408faa90..66450f8c604 100644 --- a/spec/requests/api/graphql/mutations/issues/create_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/create_spec.rb @@ -20,7 +20,8 @@ RSpec.describe 'Create an issue' do 'title' => 'new title', 'description' => 'new description', 'confidential' => true, - 'dueDate' => Date.tomorrow.strftime('%Y-%m-%d') + 'dueDate' => Date.tomorrow.strftime('%Y-%m-%d'), + 'type' => 'ISSUE' } end @@ -37,7 +38,7 @@ RSpec.describe 'Create an issue' do project.add_developer(current_user) end - it 'updates the issue' do + it 'creates the issue' do post_graphql_mutation(mutation, current_user: current_user) expect(response).to have_gitlab_http_status(:success) diff --git a/spec/requests/api/graphql/mutations/issues/update_spec.rb b/spec/requests/api/graphql/mutations/issues/update_spec.rb index 71f25dbbe49..adfa2a2bc08 100644 --- a/spec/requests/api/graphql/mutations/issues/update_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/update_spec.rb @@ -14,7 +14,8 @@ RSpec.describe 'Update of an existing issue' do 'title' => 'new title', 'description' => 'new description', 'confidential' => true, - 'dueDate' => Date.tomorrow.strftime('%Y-%m-%d') + 'dueDate' => Date.tomorrow.strftime('%Y-%m-%d'), + 'type' => 'ISSUE' } end diff --git a/yarn.lock b/yarn.lock index 0e09c594867..4684471a671 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1256,10 +1256,10 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@sourcegraph/code-host-integration@0.0.52": - version "0.0.52" - resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.52.tgz#3668364647b9248a0c43d738f7b046c551311338" - integrity sha512-HYmiGuQ3XOQHJIQaRv63Wcdl6y1rgryBrCLzJd/mG5gkkhydTqBuf3wWFKgfL3PCm026OrjXu4PvOYU1fCTZJQ== +"@sourcegraph/code-host-integration@0.0.57": + version "0.0.57" + resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.57.tgz#aed4649a51745deef5e4ee79b9a4fdc092471237" + integrity sha512-LLQp58+fqzM1IjAgti4zPwXrVVu2mNC8fpwNVnF23ead6JZPQe6Ap5fhOTZVE7ILQcFt78brGX/49Qib1Hsq0A== "@stylelint/postcss-css-in-js@^0.37.2": version "0.37.2" @@ -3605,10 +3605,10 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== -core-js@^3.1.3, core-js@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.0.tgz#62bac86f7d7f087d40dba3e90a211c2c3c8559ea" - integrity sha512-SaMnchL//WwU2Ot1hhkPflE8gzo7uq1FGvUJ8GKmi3TOU7rGTHIU+eir1WGf6qOtTyxdfdcp10yPdGZ59sQ3hw== +core-js@^3.1.3, core-js@^3.12.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.1.tgz#6b5af4ff55616c08a44d386f1f510917ff204112" + integrity sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw== core-js@~2.3.0: version "2.3.0" |