From dc22d7faa23a7988be2b70da2bfb956de4df00f0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 9 Sep 2022 21:10:12 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo/layout/argument_alignment.yml | 1 - .rubocop_todo/layout/line_length.yml | 2 - .rubocop_todo/rails/index_with.yml | 1 - .rubocop_todo/rspec/expect_in_hook.yml | 2 - .rubocop_todo/style/float_division.yml | 1 - .../style/hash_as_last_array_item.yml | 1 - .rubocop_todo/style/symbol_proc.yml | 1 - app/assets/stylesheets/pages/search.scss | 8 +- .../stylesheets/startup/startup-dark.scss | 6 + .../stylesheets/startup/startup-general.scss | 6 + app/helpers/learn_gitlab_helper.rb | 21 +- app/helpers/nav/new_dropdown_helper.rb | 2 +- app/helpers/nav/top_nav_helper.rb | 88 +++++--- app/models/ci/freeze_period_status.rb | 26 +-- .../models/onboarding/completion.rb | 13 +- .../models/onboarding/learn_gitlab.rb | 4 +- app/views/layouts/header/_default.html.haml | 20 +- .../development/new_navbar_layout.yml | 4 +- .../remove_extra_primary_submenu_options.yml | 8 + ...080630_i_quickactions_timeline_monthly.yml | 27 +++ ...7080626_i_quickactions_timeline_weekly.yml | 27 +++ ...ason_to_vulnerability_state_transitions.rb | 7 + db/schema_migrations/20220909091410 | 1 + db/structure.sql | 1 + .../incident_timeline_events.md | 6 + doc/topics/git/cherry_picking.md | 4 +- doc/topics/git/index.md | 2 +- .../index.md | 2 +- .../migrating_from_gma_to_project_template.md | 2 +- .../merge_requests/cherry_pick_changes.md | 141 +++++++----- .../img/cherry_pick_changes_commit.png | Bin 13568 -> 0 bytes .../img/cherry_pick_changes_mr.png | Bin 7214 -> 0 bytes .../img/cherry_pick_mr_timeline_v12_9.png | Bin 29557 -> 0 bytes .../img/cherry_pick_mr_timeline_v15_4.png | Bin 0 -> 7678 bytes .../merge_requests/img/cherry_pick_v15_4.png | Bin 0 -> 10187 bytes doc/user/project/quick_actions.md | 1 + doc/user/project/repository/index.md | 2 +- lib/gitlab/quick_actions/issue_actions.rb | 28 +++ .../timeline_text_and_date_time_separator.rb | 58 +++++ .../known_events/quickactions.yml | 4 + .../projects/menus/learn_gitlab_menu.rb | 4 +- locale/gitlab.pot | 18 ++ qa/qa/page/main/menu.rb | 33 ++- spec/features/admin/admin_mode_spec.rb | 4 +- .../incidents/user_uses_quick_actions_spec.rb | 26 +++ spec/features/users/show_spec.rb | 4 +- spec/helpers/learn_gitlab_helper_spec.rb | 8 +- spec/helpers/nav/new_dropdown_helper_spec.rb | 2 +- spec/helpers/nav/top_nav_helper_spec.rb | 200 +++++++++++------- ...eline_text_and_date_time_separator_spec.rb | 94 ++++++++ .../projects/menus/learn_gitlab_menu_spec.rb | 10 +- spec/models/ci/freeze_period_status_spec.rb | 9 + .../onboarding/completion_spec.rb} | 22 +- .../onboarding/learn_gitlab_spec.rb} | 20 +- spec/support/rspec_order_todo.yml | 5 - .../timeline_quick_action_shared_examples.rb | 82 +++++++ .../nav/sidebar/_project.html.haml_spec.rb | 4 +- 57 files changed, 792 insertions(+), 281 deletions(-) rename lib/learn_gitlab/onboarding.rb => app/models/onboarding/completion.rb (83%) rename lib/learn_gitlab/project.rb => app/models/onboarding/learn_gitlab.rb (95%) create mode 100644 config/feature_flags/development/remove_extra_primary_submenu_options.yml create mode 100644 config/metrics/counts_28d/20220907080630_i_quickactions_timeline_monthly.yml create mode 100644 config/metrics/counts_7d/20220907080626_i_quickactions_timeline_weekly.yml create mode 100644 db/migrate/20220909091410_add_dismissal_reason_to_vulnerability_state_transitions.rb create mode 100644 db/schema_migrations/20220909091410 delete mode 100644 doc/user/project/merge_requests/img/cherry_pick_changes_commit.png delete mode 100644 doc/user/project/merge_requests/img/cherry_pick_changes_mr.png delete mode 100644 doc/user/project/merge_requests/img/cherry_pick_mr_timeline_v12_9.png create mode 100644 doc/user/project/merge_requests/img/cherry_pick_mr_timeline_v15_4.png create mode 100644 doc/user/project/merge_requests/img/cherry_pick_v15_4.png create mode 100644 lib/gitlab/quick_actions/timeline_text_and_date_time_separator.rb create mode 100644 spec/features/incidents/user_uses_quick_actions_spec.rb create mode 100644 spec/lib/gitlab/quick_actions/timeline_text_and_date_time_separator_spec.rb rename spec/{lib/learn_gitlab/onboarding_spec.rb => models/onboarding/completion_spec.rb} (63%) rename spec/{lib/learn_gitlab/project_spec.rb => models/onboarding/learn_gitlab_spec.rb} (75%) create mode 100644 spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index 1977bedd143..bd4cd386153 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -391,7 +391,6 @@ Layout/ArgumentAlignment: - 'ee/spec/features/projects/environments/environments_spec.rb' - 'ee/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb' - 'ee/spec/features/projects/pipelines/pipeline_spec.rb' - - 'ee/spec/features/uncompleted_learn_gitlab_link_spec.rb' - 'ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb' - 'ee/spec/frontend/fixtures/search.rb' - 'ee/spec/graphql/mutations/requirements_management/export_requirements_spec.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 6d2ea48e48c..bd7bf28a1f3 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -1979,7 +1979,6 @@ Layout/LineLength: - 'ee/spec/features/search/elastic/snippet_search_spec.rb' - 'ee/spec/features/subscriptions_spec.rb' - 'ee/spec/features/trial_registrations/company_information_spec.rb' - - 'ee/spec/features/uncompleted_learn_gitlab_link_spec.rb' - 'ee/spec/features/users/login_spec.rb' - 'ee/spec/finders/analytics/devops_adoption/enabled_namespaces_finder_spec.rb' - 'ee/spec/finders/analytics/devops_adoption/snapshots_finder_spec.rb' @@ -5159,7 +5158,6 @@ Layout/LineLength: - 'spec/lib/grafana/validator_spec.rb' - 'spec/lib/kramdown/kramdown_spec.rb' - 'spec/lib/kramdown/parser/atlassian_document_format_spec.rb' - - 'spec/lib/learn_gitlab/project_spec.rb' - 'spec/lib/mattermost/command_spec.rb' - 'spec/lib/microsoft_teams/notifier_spec.rb' - 'spec/lib/object_storage/config_spec.rb' diff --git a/.rubocop_todo/rails/index_with.yml b/.rubocop_todo/rails/index_with.yml index 09339d3fd56..d8ccbd97f7c 100644 --- a/.rubocop_todo/rails/index_with.yml +++ b/.rubocop_todo/rails/index_with.yml @@ -43,7 +43,6 @@ Rails/IndexWith: - 'spec/lib/gitlab/import_export/model_configuration_spec.rb' - 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb' - 'spec/lib/google_api/cloud_platform/client_spec.rb' - - 'spec/lib/learn_gitlab/onboarding_spec.rb' - 'spec/models/event_spec.rb' - 'spec/presenters/projects/security/configuration_presenter_spec.rb' - 'spec/support/database/multiple_databases.rb' diff --git a/.rubocop_todo/rspec/expect_in_hook.yml b/.rubocop_todo/rspec/expect_in_hook.yml index 8ab50ede1ba..2353c11ce3a 100644 --- a/.rubocop_todo/rspec/expect_in_hook.yml +++ b/.rubocop_todo/rspec/expect_in_hook.yml @@ -311,12 +311,10 @@ RSpec/ExpectInHook: - 'spec/lib/gitlab/verify/uploads_spec.rb' - 'spec/lib/gitlab/zentao/query_spec.rb' - 'spec/lib/gitlab_spec.rb' - - 'spec/lib/learn_gitlab/onboarding_spec.rb' - 'spec/lib/omni_auth/strategies/jwt_spec.rb' - 'spec/lib/prometheus/pid_provider_spec.rb' - 'spec/lib/sidebars/projects/menus/external_issue_tracker_menu_spec.rb' - 'spec/lib/sidebars/projects/menus/external_wiki_menu_spec.rb' - - 'spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb' - 'spec/mailers/emails/service_desk_spec.rb' - 'spec/metrics_server/metrics_server_spec.rb' - 'spec/migrations/20210406144743_backfill_total_tuple_count_for_batched_migrations_spec.rb' diff --git a/.rubocop_todo/style/float_division.yml b/.rubocop_todo/style/float_division.yml index 201f89ac35a..7fd0cda469d 100644 --- a/.rubocop_todo/style/float_division.yml +++ b/.rubocop_todo/style/float_division.yml @@ -3,6 +3,5 @@ Style/FloatDivision: Exclude: - 'ee/app/models/geo_node_status.rb' - 'ee/app/models/namespaces/storage/root_size.rb' - - 'lib/learn_gitlab/onboarding.rb' - 'qa/qa/support/formatters/allure_metadata_formatter.rb' - 'qa/qa/tools/reliable_report.rb' diff --git a/.rubocop_todo/style/hash_as_last_array_item.yml b/.rubocop_todo/style/hash_as_last_array_item.yml index 384d2dc5fce..aa22e9ed82b 100644 --- a/.rubocop_todo/style/hash_as_last_array_item.yml +++ b/.rubocop_todo/style/hash_as_last_array_item.yml @@ -20,7 +20,6 @@ Style/HashAsLastArrayItem: - 'app/graphql/resolvers/concerns/issue_resolver_arguments.rb' - 'app/graphql/types/boards/board_issuable_input_base_type.rb' - 'app/graphql/types/boards/board_issue_input_base_type.rb' - - 'app/helpers/learn_gitlab_helper.rb' - 'app/helpers/namespaces_helper.rb' - 'app/models/customer_relations/contact.rb' - 'app/models/customer_relations/organization.rb' diff --git a/.rubocop_todo/style/symbol_proc.yml b/.rubocop_todo/style/symbol_proc.yml index 75aab7c6116..bfb3867b127 100644 --- a/.rubocop_todo/style/symbol_proc.yml +++ b/.rubocop_todo/style/symbol_proc.yml @@ -239,7 +239,6 @@ Style/SymbolProc: - 'spec/graphql/mutations/releases/create_spec.rb' - 'spec/graphql/types/work_items/widget_type_enum_spec.rb' - 'spec/helpers/instance_configuration_helper_spec.rb' - - 'spec/helpers/learn_gitlab_helper_spec.rb' - 'spec/helpers/members_helper_spec.rb' - 'spec/lib/backup/gitaly_backup_spec.rb' - 'spec/lib/gitlab/database/dynamic_model_helpers_spec.rb' diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index f65c45d6d89..e8f71c8a21c 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -49,7 +49,7 @@ input[type='checkbox']:hover { } &.header-search-is-active { - .navbar-collapse { + .global-search-container { flex-grow: 1; } @@ -59,12 +59,6 @@ input[type='checkbox']:hover { overflow: hidden; } } - - @include media-breakpoint-up(xl) { - .navbar-nav { - padding-left: 1rem; - } - } } } diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index e6ed15a86fe..0450b3d9a44 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -2088,6 +2088,12 @@ body.gl-dark { .gl-pt-0 { padding-top: 0; } +.gl-mr-auto { + margin-right: auto; +} +.gl-mr-3 { + margin-right: 0.5rem; +} .gl-ml-n2 { margin-left: -0.25rem; } diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index 5b8a3e3d5d4..356fb58b4c8 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -1739,6 +1739,12 @@ svg.s16 { .gl-pt-0 { padding-top: 0; } +.gl-mr-auto { + margin-right: auto; +} +.gl-mr-3 { + margin-right: 0.5rem; +} .gl-ml-n2 { margin-left: -0.25rem; } diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index 6f0db6cfcb6..a07922e451a 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -22,7 +22,7 @@ module LearnGitlabHelper def learn_gitlab_onboarding_available?(project) Onboarding::Progress.onboarding?(project.namespace) && - LearnGitlab::Project.new(current_user).available? + Onboarding::LearnGitlab.new(current_user).available? end private @@ -33,10 +33,12 @@ module LearnGitlabHelper action_urls(project).to_h do |action, url| [ action, - url: url, - completed: attributes[Onboarding::Progress.column_name(action)].present?, - svg: image_path("learn_gitlab/#{action}.svg"), - enabled: true + { + url: url, + completed: attributes[Onboarding::Progress.column_name(action)].present?, + svg: image_path("learn_gitlab/#{action}.svg"), + enabled: true + } ] end end @@ -70,11 +72,14 @@ module LearnGitlabHelper end def action_issue_urls - LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } + Onboarding::Completion::ACTION_ISSUE_IDS.transform_values do |id| + project_issue_url(learn_gitlab_project, id) + end end def deploy_section_action_urls(project) - experiment(:security_actions_continuous_onboarding, + experiment( + :security_actions_continuous_onboarding, namespace: project.namespace, user: current_user, sticky_to: current_user @@ -91,7 +96,7 @@ module LearnGitlabHelper end def learn_gitlab_project - @learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project + @learn_gitlab_project ||= Onboarding::LearnGitlab.new(current_user).project end def onboarding_progress(project) diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index dc7d8049556..b017c9a81d1 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -135,7 +135,7 @@ module Nav id: 'general_new_group', title: _('New group'), href: new_group_path, - data: { track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_group_link' } ) ) end diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index c4135dc086e..32d3f4aebb4 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -281,52 +281,74 @@ module Nav end def projects_submenu_items(builder:) - # These project links come from `app/views/layouts/nav/projects_dropdown/_show.html.haml` - [ - { id: 'your', title: _('Your projects'), href: dashboard_projects_path }, - { id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path }, - { id: 'explore', title: _('Explore projects'), href: explore_root_path }, - { id: 'topics', title: _('Explore topics'), href: topics_explore_projects_path } - ].each do |item| + if Feature.enabled?(:remove_extra_primary_submenu_options) + title = _('View all projects') + builder.add_primary_menu_item( - **item, - data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } + id: 'your', + title: title, + href: dashboard_projects_path, + data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } + ) + else + # These project links come from `app/views/layouts/nav/projects_dropdown/_show.html.haml` + [ + { id: 'your', title: _('Your projects'), href: dashboard_projects_path }, + { id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path }, + { id: 'explore', title: _('Explore projects'), href: explore_root_path }, + { id: 'topics', title: _('Explore topics'), href: topics_explore_projects_path } + ].each do |item| + builder.add_primary_menu_item( + **item, + data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } + ) + end + + title = _('Create new project') + + builder.add_secondary_menu_item( + id: 'create', + title: title, + href: new_project_path, + data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } ) end - - title = _('Create new project') - - builder.add_secondary_menu_item( - id: 'create', - title: title, - href: new_project_path, - data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } - ) end def groups_submenu # These group links come from `app/views/layouts/nav/groups_dropdown/_show.html.haml` builder = ::Gitlab::Nav::TopNavMenuBuilder.new - [ - { id: 'your', title: _('Your groups'), href: dashboard_groups_path }, - { id: 'explore', title: _('Explore groups'), href: explore_groups_path } - ].each do |item| + if Feature.enabled?(:remove_extra_primary_submenu_options) + title = _('View all groups') + builder.add_primary_menu_item( - **item, - data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } - ) - end - - if current_user.can_create_group? - title = _('Create group') - - builder.add_secondary_menu_item( - id: 'create', + id: 'your', title: title, - href: new_group_path, + href: dashboard_groups_path, data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } ) + else + [ + { id: 'your', title: _('Your groups'), href: dashboard_groups_path }, + { id: 'explore', title: _('Explore groups'), href: explore_groups_path } + ].each do |item| + builder.add_primary_menu_item( + **item, + data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } + ) + end + + if current_user.can_create_group? + title = _('Create group') + + builder.add_secondary_menu_item( + id: 'create', + title: title, + href: new_group_path, + data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } + ) + end end builder.build diff --git a/app/models/ci/freeze_period_status.rb b/app/models/ci/freeze_period_status.rb index befa935e750..e810bb3f229 100644 --- a/app/models/ci/freeze_period_status.rb +++ b/app/models/ci/freeze_period_status.rb @@ -13,33 +13,17 @@ module Ci end def within_freeze_period?(period) - # previous_freeze_end, ..., previous_freeze_start, ..., NOW, ..., next_freeze_end, ..., next_freeze_start - # Current time is within a freeze period if - # it falls between a previous freeze start and next freeze end - start_freeze = Gitlab::Ci::CronParser.new(period.freeze_start, period.cron_timezone) - end_freeze = Gitlab::Ci::CronParser.new(period.freeze_end, period.cron_timezone) + start_freeze_cron = Gitlab::Ci::CronParser.new(period.freeze_start, period.cron_timezone) + end_freeze_cron = Gitlab::Ci::CronParser.new(period.freeze_end, period.cron_timezone) - previous_freeze_start = previous_time(start_freeze) - previous_freeze_end = previous_time(end_freeze) - next_freeze_start = next_time(start_freeze) - next_freeze_end = next_time(end_freeze) + start_freeze = start_freeze_cron.previous_time_from(time_zone_now) + end_freeze = end_freeze_cron.next_time_from(start_freeze) - previous_freeze_end < previous_freeze_start && - previous_freeze_start <= time_zone_now && - time_zone_now <= next_freeze_end && - next_freeze_end < next_freeze_start + start_freeze <= time_zone_now && time_zone_now <= end_freeze end private - def previous_time(cron_parser) - cron_parser.previous_time_from(time_zone_now) - end - - def next_time(cron_parser) - cron_parser.next_time_from(time_zone_now) - end - def time_zone_now @time_zone_now ||= Time.zone.now end diff --git a/lib/learn_gitlab/onboarding.rb b/app/models/onboarding/completion.rb similarity index 83% rename from lib/learn_gitlab/onboarding.rb rename to app/models/onboarding/completion.rb index 4215221cdf1..49fdb102209 100644 --- a/lib/learn_gitlab/onboarding.rb +++ b/app/models/onboarding/completion.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -module LearnGitlab - class Onboarding +module Onboarding + class Completion include Gitlab::Utils::StrongMemoize include Gitlab::Experiment::Dsl @@ -24,7 +24,7 @@ module LearnGitlab @current_user = current_user end - def completed_percentage + def percentage return 0 unless onboarding_progress attributes = onboarding_progress.attributes.symbolize_keys @@ -32,14 +32,14 @@ module LearnGitlab total_actions = action_columns.count completed_actions = action_columns.count { |column| attributes[column].present? } - (completed_actions.to_f / total_actions.to_f * 100).round + (completed_actions.to_f / total_actions * 100).round end private def onboarding_progress strong_memoize(:onboarding_progress) do - ::Onboarding::Progress.find_by(namespace: namespace) # rubocop: disable CodeReuse/ActiveRecord + ::Onboarding::Progress.find_by(namespace: namespace) end end @@ -54,7 +54,8 @@ module LearnGitlab end def deploy_section_tracked_actions - experiment(:security_actions_continuous_onboarding, + experiment( + :security_actions_continuous_onboarding, namespace: namespace, user: current_user, sticky_to: current_user diff --git a/lib/learn_gitlab/project.rb b/app/models/onboarding/learn_gitlab.rb similarity index 95% rename from lib/learn_gitlab/project.rb rename to app/models/onboarding/learn_gitlab.rb index 64f91dcf1a8..d7a189ed6e2 100644 --- a/lib/learn_gitlab/project.rb +++ b/app/models/onboarding/learn_gitlab.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -module LearnGitlab - class Project +module Onboarding + class LearnGitlab PROJECT_NAME = 'Learn GitLab' PROJECT_NAME_ULTIMATE_TRIAL = 'Learn GitLab - Ultimate trial' BOARD_NAME = 'GitLab onboarding' diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 836fd3b4c31..a00c5c186cc 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -5,7 +5,7 @@ %a.gl-sr-only.gl-accessibility{ href: "#content-body" } Skip to content .container-fluid .header-content.js-header-content - .title-container.hide-when-top-nav-responsive-open.gl-transition-medium.gl-display-flex.gl-align-items-stretch.gl-pt-0 + .title-container.hide-when-top-nav-responsive-open.gl-transition-medium.gl-display-flex.gl-align-items-stretch.gl-pt-0.gl-mr-3 .title %span.gl-sr-only GitLab = link_to root_path, title: _('Dashboard'), id: 'logo', class: 'has-tooltip', **tracking_attrs('main_navigation', 'click_gitlab_logo_link', 'navigation') do @@ -28,11 +28,25 @@ .gl-display-none.gl-sm-display-block = render "layouts/nav/top_nav" - .navbar-collapse.gl-transition-medium.collapse + - if top_nav_show_search && Feature.enabled?(:new_navbar_layout) + .navbar-collapse.gl-transition-medium.collapse.gl-mr-auto.global-search-container.hide-when-top-nav-responsive-open + - search_menu_item = top_nav_search_menu_item_attrs + %ul.nav.navbar-nav.gl-w-full.gl-align-items-center + %li.nav-item.header-search-new.gl-display-none.gl-lg-display-block.gl-w-full + - unless current_controller?(:search) + - if Feature.enabled?(:new_header_search) + = render 'layouts/header_search' + - else + = render 'layouts/search' + %li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' } + = link_to search_menu_item.fetch(:href), title: search_menu_item.fetch(:title), aria: { label: search_menu_item.fetch(:title) }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = sprite_icon(search_menu_item.fetch(:icon)) + + .navbar-collapse.gl-transition-medium.collapse{ class: ('global-search-container' unless Feature.enabled?(:new_navbar_layout)) } %ul.nav.navbar-nav.gl-w-full.gl-align-items-center.gl-justify-content-end - if current_user = render 'layouts/header/new_dropdown', class: 'gl-display-none gl-sm-display-block gl-white-space-nowrap gl-text-right' - - if top_nav_show_search + - if top_nav_show_search && Feature.disabled?(:new_navbar_layout) - search_menu_item = top_nav_search_menu_item_attrs %li.nav-item.header-search-new.gl-display-none.gl-lg-display-block.gl-w-full - unless current_controller?(:search) diff --git a/config/feature_flags/development/new_navbar_layout.yml b/config/feature_flags/development/new_navbar_layout.yml index f88fe63567b..2d212922fcc 100644 --- a/config/feature_flags/development/new_navbar_layout.yml +++ b/config/feature_flags/development/new_navbar_layout.yml @@ -1,8 +1,8 @@ --- name: new_navbar_layout introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96853 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366082 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373078 milestone: '15.4' type: development group: group::foundations -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/remove_extra_primary_submenu_options.yml b/config/feature_flags/development/remove_extra_primary_submenu_options.yml new file mode 100644 index 00000000000..dda22c5d57e --- /dev/null +++ b/config/feature_flags/development/remove_extra_primary_submenu_options.yml @@ -0,0 +1,8 @@ +--- +name: remove_extra_primary_submenu_options +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96931 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373078 +milestone: '15.4' +type: development +group: group::foundations +default_enabled: true diff --git a/config/metrics/counts_28d/20220907080630_i_quickactions_timeline_monthly.yml b/config/metrics/counts_28d/20220907080630_i_quickactions_timeline_monthly.yml new file mode 100644 index 00000000000..f3f9499e9d9 --- /dev/null +++ b/config/metrics/counts_28d/20220907080630_i_quickactions_timeline_monthly.yml @@ -0,0 +1,27 @@ +--- +key_path: redis_hll_counters.quickactions.i_quickactions_timeline_monthly +name: quickactions_timeline_monthly +description: Count of MAU using the `/timeline` quick action +product_section: ops +product_stage: monitor +product_group: respond +product_category: incident_management +value_type: number +status: active +milestone: "15.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97020 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_quickactions_timeline +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20220907080626_i_quickactions_timeline_weekly.yml b/config/metrics/counts_7d/20220907080626_i_quickactions_timeline_weekly.yml new file mode 100644 index 00000000000..5b10a9a93d4 --- /dev/null +++ b/config/metrics/counts_7d/20220907080626_i_quickactions_timeline_weekly.yml @@ -0,0 +1,27 @@ +--- +key_path: redis_hll_counters.quickactions.i_quickactions_timeline_weekly +name: quickactions_timeline_weekly +description: Count of WAU using the `/timeline` quick action +product_section: ops +product_stage: monitor +product_group: respond +product_category: incident_management +value_type: number +status: active +milestone: "15.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97020 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_quickactions_timeline +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/db/migrate/20220909091410_add_dismissal_reason_to_vulnerability_state_transitions.rb b/db/migrate/20220909091410_add_dismissal_reason_to_vulnerability_state_transitions.rb new file mode 100644 index 00000000000..01fcb3aa6e1 --- /dev/null +++ b/db/migrate/20220909091410_add_dismissal_reason_to_vulnerability_state_transitions.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddDismissalReasonToVulnerabilityStateTransitions < Gitlab::Database::Migration[2.0] + def change + add_column :vulnerability_state_transitions, :dismissal_reason, :smallint + end +end diff --git a/db/schema_migrations/20220909091410 b/db/schema_migrations/20220909091410 new file mode 100644 index 00000000000..49738ad23af --- /dev/null +++ b/db/schema_migrations/20220909091410 @@ -0,0 +1 @@ +34e485c0c94960fc07a3f529aed749c2bbc1a72bb49d064225a37b85134f70f2 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index aa1548927a3..7cc22d2150b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22823,6 +22823,7 @@ CREATE TABLE vulnerability_state_transitions ( updated_at timestamp with time zone NOT NULL, author_id bigint, comment text, + dismissal_reason smallint, CONSTRAINT check_fca4a7ca39 CHECK ((char_length(comment) <= 255)) ); diff --git a/doc/operations/incident_management/incident_timeline_events.md b/doc/operations/incident_management/incident_timeline_events.md index 8ea962da35f..a360cac0d01 100644 --- a/doc/operations/incident_management/incident_timeline_events.md +++ b/doc/operations/incident_management/incident_timeline_events.md @@ -52,6 +52,12 @@ To create a timeline event: 1. Complete the required fields. 1. Select **Save** or **Save and add another event**. +### Using a quick action + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368721) in GitLab 15.4. + +You can create a timeline event using the `/timeline` [quick action](../../user/project/quick_actions.md). + ### From a comment on the incident > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344058) in GitLab 15.4. diff --git a/doc/topics/git/cherry_picking.md b/doc/topics/git/cherry_picking.md index 98458133937..d9314c3becc 100644 --- a/doc/topics/git/cherry_picking.md +++ b/doc/topics/git/cherry_picking.md @@ -17,8 +17,8 @@ and apply those changes to another branch. Cherry-picks can help you: You can cherry-pick commits from the command line. In the GitLab user interface, you can also: -- Cherry-pick [all changes from a merge request](../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-a-merge-request). -- Cherry-pick [a single commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-a-commit). +- Cherry-pick [all changes from a merge request](../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-all-changes-from-a-merge-request). +- Cherry-pick [a single commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-a-single-commit). - Cherry-pick [from a fork to the upstream repository](../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-into-a-project). ## Cherry-pick from the command line diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md index 54af1e99797..3e78e366e00 100644 --- a/doc/topics/git/index.md +++ b/doc/topics/git/index.md @@ -36,7 +36,7 @@ The following resources can help you get started with Git: - [GitLab Git Cheat Sheet (download)](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) - Commits: - [Revert a commit](../../user/project/merge_requests/revert_changes.md#revert-a-commit) - - [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-a-commit) + - [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md) - [Squashing commits](../gitlab_flow.md#squashing-commits-with-rebase) - [Squash-and-merge](../../user/project/merge_requests/squash_and_merge.md) - [Signing commits](../../user/project/repository/gpg_signed_commits/index.md) diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md index 9786d1399f7..0ed7b2b5e03 100644 --- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md +++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md @@ -209,7 +209,7 @@ To recover from multiple incorrect commits: The commits are now `A-B-C-D-E`. Alternatively, with GitLab, -you can [cherry-pick](../../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-a-commit) +you can [cherry-pick](../../../user/project/merge_requests/cherry_pick_changes.md#cherry-pick-a-single-commit) that commit into a new merge request. NOTE: diff --git a/doc/user/clusters/migrating_from_gma_to_project_template.md b/doc/user/clusters/migrating_from_gma_to_project_template.md index 9a59d135fa0..ce39e13d928 100644 --- a/doc/user/clusters/migrating_from_gma_to_project_template.md +++ b/doc/user/clusters/migrating_from_gma_to_project_template.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Migrate from GitLab Managed Apps to Cluster Management Projects **(FREE)** +# Migrate from GitLab Managed Apps to Cluster Management Projects (DEPRECATED) **(FREE)** The GitLab Managed Apps were deprecated in GitLab 14.0 in favor of user-controlled Cluster Management projects. diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md index 14f3979cf34..ade0277efa8 100644 --- a/doc/user/project/merge_requests/cherry_pick_changes.md +++ b/doc/user/project/merge_requests/cherry_pick_changes.md @@ -7,61 +7,79 @@ type: reference, concepts # Cherry-pick changes **(FREE)** -GitLab implements Git's powerful feature to -[cherry-pick any commit](https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation") -with a **Cherry-pick** button in merge requests and commit details. +In Git, *cherry-picking* is taking a single commit from one branch and adding it +as the latest commit on another branch. The rest of the commits in the source branch +are not added to the target. You should cherry-pick a commit when you need the +change contained in a single commit, but you can't or don't want to pull the +entire contents of that branch into another. -## Cherry-pick a merge request - -After the merge request has been merged, a **Cherry-pick** button displays -to cherry-pick the changes introduced by that merge request. - -![Cherry-pick merge request](img/cherry_pick_changes_mr.png) - -After you select that button, a modal displays a -[branch filter search box](../repository/branches/index.md#branch-filter-search-box) -where you can choose to either: - -- Cherry-pick the changes directly into the selected branch. -- Create a new merge request with the cherry-picked changes. - -### Track a cherry-pick - -> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2675) in GitLab 12.9. - -When you cherry-pick a merge commit, GitLab displays a system note to the related merge -request thread. It crosslinks the new commit and the existing merge request. - -![Cherry-pick tracking in merge request timeline](img/cherry_pick_mr_timeline_v12_9.png) - -Each deployment's [list of associated merge requests](../../../api/deployments.md#list-of-merge-requests-associated-with-a-deployment) includes cherry-picked merge commits. +You can use the GitLab UI to cherry-pick single commits or entire merge requests. +You can even cherry-pick a commit from [a fork of your project](#cherry-pick-into-a-project). NOTE: -We only track cherry-pick executed from GitLab (both UI and API). Support for tracking cherry-picked commits through the command line +Support for tracking commits cherry-picked from the command line is tracked [in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/202215). -## Cherry-pick a commit +## Cherry-pick all changes from a merge request -You can cherry-pick a commit from the commit details page: +After a merge request is merged, you can cherry-pick all changes introduced +by the merge request: -![Cherry-pick commit](img/cherry_pick_changes_commit.png) +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Merge requests**, and find your merge request. +1. Scroll to the merge request reports section, and find the **Merged by** report. +1. In the top right, select **Cherry-pick**: -Similar to cherry-picking a merge request, you can cherry-pick the changes -directly into the target branch or create a new merge request to cherry-pick the -changes. + ![Cherry-pick merge request](img/cherry_pick_v15_4.png) +1. In the modal window, select the project and branch to cherry-pick into. +1. Optional. Select **Start a new merge request with these changes**. +1. Select **Cherry-pick**. -When cherry-picking merge commits, the mainline is always the -first parent. If you want to use a different mainline, you need to do that -from the command line. +## Cherry-pick a single commit -Here's a quick example to cherry-pick a merge commit using the second parent as the -mainline: +You can cherry-pick a single commit from multiple locations in your GitLab project. -```shell -git cherry-pick -m 2 7a39eb0 -``` +### From a project's commit list -### Cherry-pick into a project +To cherry-pick a commit from the list of all commits for a project: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Repository > Commits**. +1. Select the title of the commit you want to cherry-pick. +1. In the modal window, select the project and branch to cherry-pick into. +1. Optional. Select **Start a new merge request with these changes**. +1. Select **Cherry-pick**. + +### From a merge request + +You can cherry-pick commits from any merge request in your project, regardless of +whether the merge request is open or closed. To cherry-pick a commit from the +list of commits included in a merge request: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Merge requests**, and find your merge request. +1. In the merge request's secondary menu, select **Commits** to display the commit details page. +1. Select the title of the commit you want to cherry-pick. +1. In the top right corner, select **Options > Cherry-pick** to show the cherry-pick modal. +1. In the modal window, select the project and branch to cherry-pick into. +1. Optional. Select **Start a new merge request with these changes**. +1. Select **Cherry-pick**. + +### From the file view of a repository + +You can cherry-pick from the list of previous commits affecting an individual file +when you view that file in your project's Git repository: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Repository > Files** and go to the file + changed by the commit. +1. Select **History**, then select the title of the commit you want to cherry-pick. +1. In the top right corner, select **Options > Cherry-pick** to show the cherry-pick modal. +1. In the modal window, select the project and branch to cherry-pick into. +1. Optional. Select **Start a new merge request with these changes**. +1. Select **Cherry-pick**. + +## Cherry-pick into a project > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21268) in GitLab 13.11 behind a [feature flag](../../feature_flags.md), disabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/324154) in GitLab 14.0. @@ -70,25 +88,38 @@ You can cherry-pick merge requests from the same project, or forks of the same project, from the GitLab user interface: 1. In the merge request's secondary menu, select **Commits** to display the commit details page. -1. Select the **Options** dropdown and select **Cherry-pick** to show the cherry-pick modal. +1. In the top right corner, select **Options > Cherry-pick** to show the cherry-pick modal. 1. In **Pick into project** and **Pick into branch**, select the destination project and branch: ![Cherry-pick commit](img/cherry_pick_into_project_v13_11.png) 1. Optional. Select **Start a new merge request** if you're ready to create a merge request. 1. Select **Cherry-pick**. +## View system notes for cherry-picked commits + +When you cherry-pick a merge commit in the GitLab UI or API, GitLab adds a system note +to the related merge request thread in the format **{cherry-pick-commit}** +`[USER]` **picked the changes into the branch** `[BRANCHNAME]` with commit** `[SHA]` `[DATE]`: + +![Cherry-pick tracking in merge request timeline](img/cherry_pick_mr_timeline_v15_4.png) + +The system note crosslinks the new commit and the existing merge request. +Each deployment's [list of associated merge requests](../../../api/deployments.md#list-of-merge-requests-associated-with-a-deployment) includes cherry-picked merge commits. + ## Related topics -- The [Commits API](../../../api/commits.md) enables you to add custom messages - to changes you cherry-pick through the API. +- Use the [Commits API](../../../api/commits.md) to add custom messages + to changes when you use the API to cherry-pick. - +When you cherry-pick a merge commit in the GitLab UI, the mainline is always the +first parent. Use the command line to cherry-pick with a different mainline. + +Here's a quick example to cherry-pick a merge commit using the second parent as the +mainline: + +```shell +git cherry-pick -m 2 7a39eb0 +``` diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png deleted file mode 100644 index c98821548f88c4e59298a42065dd3ce55595a975..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13568 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 YV+6Na)OPPNEKlQA)V*mgE diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png deleted file mode 100644 index 8b51503419bcf3f03d23d9b3a961e6b8849e1d13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7214 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 z 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 M)>5iauz3If0A$iZh5!Hn literal 0 HcmV?d00001 diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index fe2d3cd6cd1..af43d53679b 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -110,6 +110,7 @@ threads. Some quick actions might not be available to all subscription tiers. | `/tableflip ` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`. | | `/target_branch ` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set target branch. | | `/title ` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. | +| `/timeline \| ` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a timeline event to this incident. For example, `/timeline DB load spiked \| 2022-09-07 09:30`. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368721) in GitLab 15.4). | | `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. | | `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) in GitLab 14.3 | | `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific assignees. | diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 40584541e53..4926cf3812e 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -49,7 +49,7 @@ to a branch in the repository. When you use the command line, you can commit mul on their respective thread. - **Cherry-pick a commit:** In GitLab, you can - [cherry-pick a commit](../merge_requests/cherry_pick_changes.md#cherry-pick-a-commit) + [cherry-pick a commit](../merge_requests/cherry_pick_changes.md#cherry-pick-a-single-commit) from the UI. - **Revert a commit:** [Revert a commit](../merge_requests/revert_changes.md#revert-a-commit) diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb index 1d122bb2b6e..4883c649a62 100644 --- a/lib/gitlab/quick_actions/issue_actions.rb +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -317,6 +317,30 @@ module Gitlab command :remove_contacts do |contact_emails| @updates[:remove_contacts] = contact_emails.split(' ') end + + desc { _('Add a timeline event to incident') } + explanation { _('Adds a timeline event to incident.') } + params ' | ' + types Issue + condition do + quick_action_target.incident? && + current_user.can?(:admin_incident_management_timeline_event, quick_action_target) + end + parse_params do |event_params| + Gitlab::QuickActions::TimelineTextAndDateTimeSeparator.new(event_params).execute + end + command :timeline do |event_text, date_time| + if event_text && date_time + timeline_event = timeline_event_create_service(event_text, date_time).execute + + @execution_message[:timeline] = + if timeline_event.success? + _('Timeline event added successfully.') + else + _('Something went wrong while adding timeline event.') + end + end + end end private @@ -336,6 +360,10 @@ module Gitlab def merge_updates(result, update_hash) update_hash.merge!(result.payload) if result.payload end + + def timeline_event_create_service(event_text, event_date_time) + ::IncidentManagement::TimelineEvents::CreateService.new(quick_action_target, current_user, { note: event_text, occurred_at: event_date_time, editable: true }) + end end end end diff --git a/lib/gitlab/quick_actions/timeline_text_and_date_time_separator.rb b/lib/gitlab/quick_actions/timeline_text_and_date_time_separator.rb new file mode 100644 index 00000000000..e8002656ff5 --- /dev/null +++ b/lib/gitlab/quick_actions/timeline_text_and_date_time_separator.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + class TimelineTextAndDateTimeSeparator + DATETIME_REGEX = %r{(\d{2,4}[\-.]\d{1,2}[\-.]\d{1,2} \d{1,2}:\d{2})}.freeze + MIXED_DELIMITER = %r{([/.])}.freeze + TIME_REGEX = %r{(\d{1,2}:\d{2})}.freeze + + def initialize(timeline_event_arg) + @timeline_event_arg = timeline_event_arg + @timeline_text = get_text + @timeline_date_string = get_raw_date_string + end + + def execute + return if @timeline_event_arg.blank? + return if date_contains_mixed_delimiters? + return [@timeline_text, get_current_date_time] unless date_time_present? + return unless valid_date? + + [@timeline_text, get_actual_date_time] + end + + private + + def get_text + @timeline_event_arg.split('|')[0]&.strip + end + + def get_raw_date_string + @timeline_event_arg.split('|')[1]&.strip + end + + def get_current_date_time + DateTime.current.strftime("%Y-%m-%d %H:%M:00 UTC") + end + + def get_actual_date_time + DateTime.parse(@timeline_date_string) + end + + def date_time_present? + DATETIME_REGEX =~ @timeline_date_string || TIME_REGEX =~ @timeline_date_string + end + + def date_contains_mixed_delimiters? + MIXED_DELIMITER =~ @timeline_date_string + end + + def valid_date? + get_actual_date_time + rescue Date::Error + nil + end + end + end +end diff --git a/lib/gitlab/usage_data_counters/known_events/quickactions.yml b/lib/gitlab/usage_data_counters/known_events/quickactions.yml index 007401ecd44..58a0c0695af 100644 --- a/lib/gitlab/usage_data_counters/known_events/quickactions.yml +++ b/lib/gitlab/usage_data_counters/known_events/quickactions.yml @@ -127,6 +127,10 @@ category: quickactions redis_slot: quickactions aggregation: weekly +- name: i_quickactions_timeline + category: quickactions + redis_slot: quickactions + aggregation: weekly - name: i_quickactions_page category: quickactions redis_slot: quickactions diff --git a/lib/sidebars/projects/menus/learn_gitlab_menu.rb b/lib/sidebars/projects/menus/learn_gitlab_menu.rb index d2bc2fa0681..b6fae2af93d 100644 --- a/lib/sidebars/projects/menus/learn_gitlab_menu.rb +++ b/lib/sidebars/projects/menus/learn_gitlab_menu.rb @@ -29,10 +29,10 @@ module Sidebars override :pill_count def pill_count strong_memoize(:pill_count) do - percentage = LearnGitlab::Onboarding.new( + percentage = Onboarding::Completion.new( context.project.namespace, context.current_user - ).completed_percentage + ).percentage "#{percentage}%" end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2a82ce046a8..e7b02fc8ab4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2201,6 +2201,9 @@ msgstr "" msgid "Add a table" msgstr "" +msgid "Add a timeline event to incident" +msgstr "" + msgid "Add a title..." msgstr "" @@ -2471,6 +2474,9 @@ msgstr "" msgid "Adds a Zoom meeting." msgstr "" +msgid "Adds a timeline event to incident." +msgstr "" + msgid "Adds a to do." msgstr "" @@ -37111,6 +37117,9 @@ msgstr "" msgid "Something went wrong when reordering designs. Please try again" msgstr "" +msgid "Something went wrong while adding timeline event." +msgstr "" + msgid "Something went wrong while adding your award. Please try again." msgstr "" @@ -40971,6 +40980,9 @@ msgstr "" msgid "Timeago|right now" msgstr "" +msgid "Timeline event added successfully." +msgstr "" + msgid "Timeline|Turn recent updates view off" msgstr "" @@ -43386,9 +43398,15 @@ msgstr "" msgid "View all environments." msgstr "" +msgid "View all groups" +msgstr "" + msgid "View all issues" msgstr "" +msgid "View all projects" +msgstr "" + msgid "View blame" msgstr "" diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 90b419f8cef..aaf10e12e82 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -53,28 +53,45 @@ module QA element :search_term_field end + view 'app/views/layouts/header/_new_dropdown.html.haml' do + element :new_menu_toggle + end + + view 'app/helpers/nav/new_dropdown_helper.rb' do + element :global_new_group_link + element :global_new_project_link + end + def go_to_groups within_groups_menu do - click_element(:menu_item_link, title: 'Your groups') + # Remove if statement once :remove_extra_primary_submenu_options ff is enabled by default + if has_element?(:menu_item_link, title: 'Your groups') + click_element(:menu_item_link, title: 'Your groups') + else + click_element(:menu_item_link, title: 'View all groups') + end end end def go_to_create_group - within_groups_menu do - click_element(:menu_item_link, title: 'Create group') - end + click_element(:new_menu_toggle) + click_element(:global_new_group_link) end def go_to_projects within_projects_menu do - click_element(:menu_item_link, title: 'Your projects') + # Remove if statement once :remove_extra_primary_submenu_options ff is enabled by default + if has_element?(:menu_item_link, title: 'Your projects') + click_element(:menu_item_link, title: 'Your projects') + else + click_element(:menu_item_link, title: 'View all projects') + end end end def go_to_create_project - within_projects_menu do - click_element(:menu_item_link, title: 'Create new project') - end + click_element(:new_menu_toggle) + click_element(:global_new_project_link) end def go_to_menu_dropdown_option(option_name) diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb index 24a10d3677d..33cf0e8c4f8 100644 --- a/spec/features/admin/admin_mode_spec.rb +++ b/spec/features/admin/admin_mode_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Admin mode', :js do open_top_nav_projects within_top_nav do - click_link('Your projects') + click_link('View all projects') end expect(page).to have_current_path(dashboard_projects_path) @@ -99,7 +99,7 @@ RSpec.describe 'Admin mode', :js do open_top_nav_projects within_top_nav do - click_link('Your projects') + click_link('View all projects') end expect(page).to have_current_path(dashboard_projects_path) diff --git a/spec/features/incidents/user_uses_quick_actions_spec.rb b/spec/features/incidents/user_uses_quick_actions_spec.rb new file mode 100644 index 00000000000..fce9eadd42f --- /dev/null +++ b/spec/features/incidents/user_uses_quick_actions_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Incidents > User uses quick actions', :js do + include Spec::Support::Helpers::Features::NotesHelpers + + describe 'incident-only commands' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:issue, reload: true) { create(:incident, project: project) } + + before do + project.add_developer(user) + sign_in(user) + visit project_issue_path(project, issue) + wait_for_all_requests + end + + after do + wait_for_requests + end + + it_behaves_like 'timeline quick action' + end +end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 068e1fd4243..bbf5882f89f 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -339,7 +339,7 @@ RSpec.describe 'User page' do subject - page.within '.navbar-nav' do + page.within '.navbar-gitlab' do expect(page).to have_link('Sign in') end end @@ -351,7 +351,7 @@ RSpec.describe 'User page' do subject - page.within '.navbar-nav' do + page.within '.navbar-gitlab' do expect(page).to have_link('Sign in / Register') end end diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb index 68f847e80b0..0d4f1965d92 100644 --- a/spec/helpers/learn_gitlab_helper_spec.rb +++ b/spec/helpers/learn_gitlab_helper_spec.rb @@ -7,11 +7,11 @@ RSpec.describe LearnGitlabHelper do include Devise::Test::ControllerHelpers let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME, namespace: user.namespace) } + let_it_be(:project) { create(:project, name: Onboarding::LearnGitlab::PROJECT_NAME, namespace: user.namespace) } let_it_be(:namespace) { project.namespace } before do - allow_next_instance_of(LearnGitlab::Project) do |learn_gitlab| + allow_next_instance_of(Onboarding::LearnGitlab) do |learn_gitlab| allow(learn_gitlab).to receive(:project).and_return(project) end @@ -38,7 +38,7 @@ RSpec.describe LearnGitlabHelper do with_them do before do allow(Onboarding::Progress).to receive(:onboarding?).with(project.namespace).and_return(onboarding) - allow_next(LearnGitlab::Project, user).to receive(:available?).and_return(learn_gitlab_available) + allow_next(Onboarding::LearnGitlab, user).to receive(:available?).and_return(learn_gitlab_available) end context 'when signed in' do @@ -81,7 +81,7 @@ RSpec.describe LearnGitlabHelper do it 'has all section data', :aggregate_failures do expect(onboarding_sections_data.keys).to contain_exactly(:deploy, :plan, :workspace) - expect(onboarding_sections_data.values.map { |section| section.keys }).to match_array([[:svg]] * 3) + expect(onboarding_sections_data.values.map(&:keys)).to match_array([[:svg]] * 3) end it 'has all project data', :aggregate_failures do diff --git a/spec/helpers/nav/new_dropdown_helper_spec.rb b/spec/helpers/nav/new_dropdown_helper_spec.rb index 45664a7e0bd..3a65131aab0 100644 --- a/spec/helpers/nav/new_dropdown_helper_spec.rb +++ b/spec/helpers/nav/new_dropdown_helper_spec.rb @@ -100,7 +100,7 @@ RSpec.describe Nav::NewDropdownHelper do id: 'general_new_group', title: 'New group', href: '/groups/new', - data: { track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown' } + data: { qa_selector: 'global_new_group_link', track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown' } ) ) ) diff --git a/spec/helpers/nav/top_nav_helper_spec.rb b/spec/helpers/nav/top_nav_helper_spec.rb index dc8f062ded8..9c396d6bf25 100644 --- a/spec/helpers/nav/top_nav_helper_spec.rb +++ b/spec/helpers/nav/top_nav_helper_spec.rb @@ -161,61 +161,87 @@ RSpec.describe Nav::TopNavHelper do ::Gitlab::Nav::TopNavMenuItem.build( data: { qa_selector: 'menu_item_link', - qa_title: 'Your projects', - **menu_data_tracking_attrs('your_projects') + qa_title: 'View all projects', + **menu_data_tracking_attrs('view_all_projects') }, href: '/dashboard/projects', id: 'your', - title: 'Your projects' - ), - ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'menu_item_link', - qa_title: 'Starred projects', - **menu_data_tracking_attrs('starred_projects') - }, - href: '/dashboard/projects/starred', - id: 'starred', - title: 'Starred projects' - ), - ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'menu_item_link', - qa_title: 'Explore projects', - **menu_data_tracking_attrs('explore_projects') - }, - href: '/explore', - id: 'explore', - title: 'Explore projects' - ), - ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'menu_item_link', - qa_title: 'Explore topics', - **menu_data_tracking_attrs('explore_topics') - }, - href: '/explore/projects/topics', - id: 'topics', - title: 'Explore topics' + title: 'View all projects' ) ] expect(projects_view[:linksPrimary]).to eq(expected_links_primary) end - it 'has expected :linksSecondary' do - expected_links_secondary = [ - ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'menu_item_link', - qa_title: 'Create new project', - **menu_data_tracking_attrs('create_new_project') - }, - href: '/projects/new', - id: 'create', - title: 'Create new project' - ) - ] - expect(projects_view[:linksSecondary]).to eq(expected_links_secondary) + it 'does not have any :linksSecondary' do + expect(projects_view[:linksSecondary]).to eq([]) + end + + context 'when extra submenu options are not hidden' do + before do + stub_feature_flags(remove_extra_primary_submenu_options: false) + end + + it 'has expected :linksPrimary' do + expected_links_primary = [ + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Your projects', + **menu_data_tracking_attrs('your_projects') + }, + href: '/dashboard/projects', + id: 'your', + title: 'Your projects' + ), + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Starred projects', + **menu_data_tracking_attrs('starred_projects') + }, + href: '/dashboard/projects/starred', + id: 'starred', + title: 'Starred projects' + ), + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Explore projects', + **menu_data_tracking_attrs('explore_projects') + }, + href: '/explore', + id: 'explore', + title: 'Explore projects' + ), + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Explore topics', + **menu_data_tracking_attrs('explore_topics') + }, + href: '/explore/projects/topics', + id: 'topics', + title: 'Explore topics' + ) + ] + expect(projects_view[:linksPrimary]).to eq(expected_links_primary) + end + + it 'has expected :linksSecondary' do + expected_links_secondary = [ + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Create new project', + **menu_data_tracking_attrs('create_new_project') + }, + href: '/projects/new', + id: 'create', + title: 'Create new project' + ) + ] + expect(projects_view[:linksSecondary]).to eq(expected_links_secondary) + end end context 'with current nav as project' do @@ -300,41 +326,67 @@ RSpec.describe Nav::TopNavHelper do ::Gitlab::Nav::TopNavMenuItem.build( data: { qa_selector: 'menu_item_link', - qa_title: 'Your groups', - **menu_data_tracking_attrs('your_groups') + qa_title: 'View all groups', + **menu_data_tracking_attrs('view_all_groups') }, href: '/dashboard/groups', id: 'your', - title: 'Your groups' - ), - ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'menu_item_link', - qa_title: 'Explore groups', - **menu_data_tracking_attrs('explore_groups') - }, - href: '/explore/groups', - id: 'explore', - title: 'Explore groups' + title: 'View all groups' ) ] expect(groups_view[:linksPrimary]).to eq(expected_links_primary) end - it 'has expected :linksSecondary' do - expected_links_secondary = [ - ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'menu_item_link', - qa_title: 'Create group', - **menu_data_tracking_attrs('create_group') - }, - href: '/groups/new', - id: 'create', - title: 'Create group' - ) - ] - expect(groups_view[:linksSecondary]).to eq(expected_links_secondary) + it 'does not have any :linksSecondary' do + expect(groups_view[:linksSecondary]).to eq([]) + end + + context 'when extra submenu options are not hidden' do + before do + stub_feature_flags(remove_extra_primary_submenu_options: false) + end + + it 'has expected :linksPrimary' do + expected_links_primary = [ + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Your groups', + **menu_data_tracking_attrs('your_groups') + }, + href: '/dashboard/groups', + id: 'your', + title: 'Your groups' + ), + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Explore groups', + **menu_data_tracking_attrs('explore_groups') + }, + href: '/explore/groups', + id: 'explore', + title: 'Explore groups' + ) + ] + expect(groups_view[:linksPrimary]).to eq(expected_links_primary) + end + + it 'has expected :linksSecondary' do + expected_links_secondary = [ + ::Gitlab::Nav::TopNavMenuItem.build( + data: { + qa_selector: 'menu_item_link', + qa_title: 'Create group', + **menu_data_tracking_attrs('create_group') + }, + href: '/groups/new', + id: 'create', + title: 'Create group' + ) + ] + expect(groups_view[:linksSecondary]).to eq(expected_links_secondary) + end end context 'with external user' do diff --git a/spec/lib/gitlab/quick_actions/timeline_text_and_date_time_separator_spec.rb b/spec/lib/gitlab/quick_actions/timeline_text_and_date_time_separator_spec.rb new file mode 100644 index 00000000000..89fe19b8f60 --- /dev/null +++ b/spec/lib/gitlab/quick_actions/timeline_text_and_date_time_separator_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::QuickActions::TimelineTextAndDateTimeSeparator do + subject(:timeline_text_and_datetime_separator) { described_class } + + shared_examples 'arg line with invalid parameters' do + it 'returns nil' do + expect(timeline_text_and_datetime_separator.new(invalid_arg).execute).to eq(nil) + end + end + + shared_examples 'arg line with valid parameters' do + it 'returns text and date time array' do + freeze_time do + expect(timeline_text_and_datetime_separator.new(valid_arg).execute).to eq(expected_response) + end + end + end + + describe 'execute' do + context 'with invalid parameters in arg line' do + context 'with empty arg line' do + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { '' } + end + end + + context 'with invalid date' do + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { 'timeline comment | 2022-13-13 09:30' } + end + + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { 'timeline comment | 2022-09/09 09:30' } + end + + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { 'timeline comment | 2022-09.09 09:30' } + end + end + + context 'with invalid time' do + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { 'timeline comment | 2022-11-13 29:30' } + end + end + + context 'when date is invalid in arg line' do + let(:invalid_arg) { 'timeline comment | wrong data type' } + + it 'return current date' do + timeline_args = timeline_text_and_datetime_separator.new(invalid_arg).execute + + expect(timeline_args).to be_an_instance_of(Array) + expect(timeline_args.first).to eq('timeline comment') + expect(timeline_args.second).to match(Gitlab::QuickActions::TimelineTextAndDateTimeSeparator::DATETIME_REGEX) + end + end + end + + context 'with valid parameters' do + context 'when only timeline text present in arg line' do + it_behaves_like 'arg line with valid parameters' do + let(:timeline_text) { 'timeline comment' } + let(:valid_arg) { timeline_text } + let(:date) { DateTime.current.strftime("%Y-%m-%d %H:%M:00 UTC") } + let(:expected_response) { [timeline_text, date] } + end + end + + context 'when only timeline text and time present in arg line' do + it_behaves_like 'arg line with valid parameters' do + let(:timeline_text) { 'timeline comment' } + let(:date) { '09:30' } + let(:valid_arg) { "#{timeline_text} | #{date}" } + let(:parsed_date) { DateTime.parse(date) } + let(:expected_response) { [timeline_text, parsed_date] } + end + end + + context 'when timeline text and date is present in arg line' do + it_behaves_like 'arg line with valid parameters' do + let(:timeline_text) { 'timeline comment' } + let(:date) { '2022-06-05 09:30' } + let(:valid_arg) { "#{timeline_text} | #{date}" } + let(:parsed_date) { DateTime.parse(date) } + let(:expected_response) { [timeline_text, parsed_date] } + end + end + end + end +end diff --git a/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb b/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb index 36a76e70a48..4ae29f28f3a 100644 --- a/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb @@ -68,13 +68,11 @@ RSpec.describe Sidebars::Projects::Menus::LearnGitlabMenu do end describe '#pill_count' do - before do - expect_next_instance_of(LearnGitlab::Onboarding) do |onboarding| - expect(onboarding).to receive(:completed_percentage).and_return(20) - end - end - it 'returns pill count' do + expect_next_instance_of(Onboarding::Completion) do |onboarding| + expect(onboarding).to receive(:percentage).and_return(20) + end + expect(subject.pill_count).to eq '20%' end end diff --git a/spec/models/ci/freeze_period_status_spec.rb b/spec/models/ci/freeze_period_status_spec.rb index f51381f7a5f..ecbb7af64f7 100644 --- a/spec/models/ci/freeze_period_status_spec.rb +++ b/spec/models/ci/freeze_period_status_spec.rb @@ -59,4 +59,13 @@ RSpec.describe Ci::FreezePeriodStatus do it_behaves_like 'outside freeze period', Time.utc(2020, 4, 13, 8, 1) end + + # https://gitlab.com/gitlab-org/gitlab/-/issues/370472 + context 'when period overlaps with itself' do + let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '* * * 8 *', freeze_end: '* * * 10 *') } + + it_behaves_like 'within freeze period', Time.utc(2020, 8, 11, 0, 0) + + it_behaves_like 'outside freeze period', Time.utc(2020, 10, 11, 0, 0) + end end diff --git a/spec/lib/learn_gitlab/onboarding_spec.rb b/spec/models/onboarding/completion_spec.rb similarity index 63% rename from spec/lib/learn_gitlab/onboarding_spec.rb rename to spec/models/onboarding/completion_spec.rb index 97926f8a612..e1fad4255bc 100644 --- a/spec/lib/learn_gitlab/onboarding_spec.rb +++ b/spec/models/onboarding/completion_spec.rb @@ -2,13 +2,11 @@ require 'spec_helper' -RSpec.describe LearnGitlab::Onboarding do - describe '#completed_percentage' do +RSpec.describe Onboarding::Completion do + describe '#percentage' do let(:completed_actions) { {} } - let(:onboarding_progress) { build(:onboarding_progress, namespace: namespace, **completed_actions) } - let(:namespace) { create(:namespace) } - - let_it_be(:tracked_action_columns) do + let!(:onboarding_progress) { create(:onboarding_progress, namespace: namespace, **completed_actions) } + let(:tracked_action_columns) do [ *described_class::ACTION_ISSUE_IDS.keys, *described_class::ACTION_PATHS, @@ -16,14 +14,12 @@ RSpec.describe LearnGitlab::Onboarding do ].map { |key| ::Onboarding::Progress.column_name(key) } end - before do - expect(::Onboarding::Progress).to receive(:find_by).with(namespace: namespace).and_return(onboarding_progress) - end + let_it_be(:namespace) { create(:namespace) } - subject { described_class.new(namespace).completed_percentage } + subject { described_class.new(namespace).percentage } context 'when no onboarding_progress exists' do - let(:onboarding_progress) { nil } + subject { described_class.new(build(:namespace)).percentage } it { is_expected.to eq(0) } end @@ -34,13 +30,13 @@ RSpec.describe LearnGitlab::Onboarding do context 'when all tracked actions have been completed' do let(:completed_actions) do - tracked_action_columns.to_h { |action| [action, Time.current] } + tracked_action_columns.index_with { Time.current } end it { is_expected.to eq(100) } end - describe 'security_actions_continuous_onboarding experiment' do + context 'with security_actions_continuous_onboarding experiment' do let(:completed_actions) { Hash[tracked_action_columns.first, Time.current] } context 'when control' do diff --git a/spec/lib/learn_gitlab/project_spec.rb b/spec/models/onboarding/learn_gitlab_spec.rb similarity index 75% rename from spec/lib/learn_gitlab/project_spec.rb rename to spec/models/onboarding/learn_gitlab_spec.rb index 23784709817..5e3e1f9c304 100644 --- a/spec/lib/learn_gitlab/project_spec.rb +++ b/spec/models/onboarding/learn_gitlab_spec.rb @@ -2,17 +2,17 @@ require 'spec_helper' -RSpec.describe LearnGitlab::Project do +RSpec.describe Onboarding::LearnGitlab do let_it_be(:current_user) { create(:user) } - let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME) } - let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: LearnGitlab::Project::BOARD_NAME) } - let_it_be(:learn_gitlab_label) { create(:label, project: learn_gitlab_project, name: LearnGitlab::Project::LABEL_NAME) } + let_it_be(:learn_gitlab_project) { create(:project, name: described_class::PROJECT_NAME) } + let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: described_class::BOARD_NAME) } + let_it_be(:learn_gitlab_label) { create(:label, project: learn_gitlab_project, name: described_class::LABEL_NAME) } before do learn_gitlab_project.add_developer(current_user) end - describe '.available?' do + describe '#available?' do using RSpec::Parameterized::TableSyntax where(:project, :board, :label, :expected_result) do @@ -41,25 +41,27 @@ RSpec.describe LearnGitlab::Project do end end - describe '.project' do + describe '#project' do subject { described_class.new(current_user).project } it { is_expected.to eq learn_gitlab_project } context 'when it is created during trial signup' do - let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME_ULTIMATE_TRIAL, path: 'learn-gitlab-ultimate-trial') } + let_it_be(:learn_gitlab_project) do + create(:project, name: described_class::PROJECT_NAME_ULTIMATE_TRIAL, path: 'learn-gitlab-ultimate-trial') + end it { is_expected.to eq learn_gitlab_project } end end - describe '.board' do + describe '#board' do subject { described_class.new(current_user).board } it { is_expected.to eq learn_gitlab_board } end - describe '.label' do + describe '#label' do subject { described_class.new(current_user).label } it { is_expected.to eq learn_gitlab_label } diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index f297734b4b7..9d54e746d63 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -537,7 +537,6 @@ - './ee/spec/features/trials/capture_lead_spec.rb' - './ee/spec/features/trials/select_namespace_spec.rb' - './ee/spec/features/trials/show_trial_banner_spec.rb' -- './ee/spec/features/uncompleted_learn_gitlab_link_spec.rb' - './ee/spec/features/users/arkose_labs_csp_spec.rb' - './ee/spec/features/users/login_spec.rb' - './ee/spec/features/users/signup_spec.rb' @@ -5459,7 +5458,6 @@ - './spec/helpers/keyset_helper_spec.rb' - './spec/helpers/labels_helper_spec.rb' - './spec/helpers/lazy_image_tag_helper_spec.rb' -- './spec/helpers/learn_gitlab_helper_spec.rb' - './spec/helpers/listbox_helper_spec.rb' - './spec/helpers/markup_helper_spec.rb' - './spec/helpers/members_helper_spec.rb' @@ -7756,8 +7754,6 @@ - './spec/lib/json_web_token/token_spec.rb' - './spec/lib/kramdown/kramdown_spec.rb' - './spec/lib/kramdown/parser/atlassian_document_format_spec.rb' -- './spec/lib/learn_gitlab/onboarding_spec.rb' -- './spec/lib/learn_gitlab/project_spec.rb' - './spec/lib/marginalia_spec.rb' - './spec/lib/mattermost/client_spec.rb' - './spec/lib/mattermost/command_spec.rb' @@ -7824,7 +7820,6 @@ - './spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb' - './spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb' - './spec/lib/sidebars/projects/menus/issues_menu_spec.rb' -- './spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb' - './spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb' - './spec/lib/sidebars/projects/menus/monitor_menu_spec.rb' - './spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb' diff --git a/spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ae7e511a739 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'timeline quick action' do + describe '/timeline' do + context 'with valid args' do + where(:timeline_text, :date_time_arg) do + [ + ['timeline comment', '2022-09-09 09:30'], + ['new timeline comment', '09:30'], + ['another timeline comment', ' 2022-09-09 09:15'] + ] + end + + with_them do + it 'adds a timeline event' do + add_note("/timeline #{timeline_text} | #{date_time_arg}") + + expect(page).to have_content('Timeline event added successfully.') + expect(issue.incident_management_timeline_events.first.note).to eq(timeline_text) + expect(issue.incident_management_timeline_events.first.occurred_at).to eq(DateTime.parse(date_time_arg)) + end + end + + it 'adds a timeline event when no date is passed' do + freeze_time do + add_note('/timeline timeline event with not date') + + expect(page).to have_content('Timeline event added successfully.') + expect(issue.incident_management_timeline_events.first.note).to eq('timeline event with not date') + expect(issue.incident_management_timeline_events.first.occurred_at).to eq(DateTime + .current.strftime("%Y-%m-%d %H:%M:00 UTC")) + end + end + + it 'adds a timeline event when only date is passed' do + freeze_time do + add_note('/timeline timeline event with not date | 2022-10-11') + + expect(page).to have_content('Timeline event added successfully.') + expect(issue.incident_management_timeline_events.first.note).to eq('timeline event with not date') + expect(issue.incident_management_timeline_events.first.occurred_at).to eq(DateTime + .current.strftime("%Y-%m-%d %H:%M:00 UTC")) + end + end + end + + context 'with invalid args' do + where(:timeline_text, :date_time_arg) do + [ + ['timeline comment', '2022-13-13 09:30'], + ['timeline comment 2', '2022-09-06 24:30'] + ] + end + + with_them do + it 'does not add a timeline event' do + add_note("/timeline #{timeline_text} | #{date_time_arg}") + + expect(page).to have_content('Failed to apply commands.') + expect(issue.incident_management_timeline_events.length).to eq(0) + end + end + end + + context 'when create service fails' do + before do + allow_next_instance_of(::IncidentManagement::TimelineEvents::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error(payload: { timeline_event: nil }, message: 'Some error') + ) + end + end + + it 'does not add a timeline event' do + add_note('/timeline text | 2022-09-10 09:30') + + expect(page).to have_content('Something went wrong while adding timeline event.') + expect(issue.incident_management_timeline_events.length).to eq(0) + end + end + end +end diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index f5cd5679270..e7d9a8a4708 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -70,8 +70,8 @@ RSpec.describe 'layouts/nav/sidebar/_project' do describe 'Learn GitLab' do it 'has a link to the learn GitLab' do allow(view).to receive(:learn_gitlab_enabled?).and_return(true) - allow_next_instance_of(LearnGitlab::Onboarding) do |onboarding| - expect(onboarding).to receive(:completed_percentage).and_return(20) + allow_next_instance_of(Onboarding::Completion) do |onboarding| + expect(onboarding).to receive(:percentage).and_return(20) end render