Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-19 03:09:03 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-19 03:09:03 +0300
commitcd49d91db362a479c1b53de07741713f7e7ea2cc (patch)
tree6d5f244f9d6e805a15ffc93172454788658af1d7
parent705210af74e93f829f59e00f311cba07ea8bd5c7 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue8
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue24
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue99
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js2
-rw-r--r--app/assets/stylesheets/page_bundles/learn_gitlab.scss8
-rw-r--r--app/controllers/projects/learn_gitlab_controller.rb5
-rw-r--r--app/experiments/video_tutorials_continuous_onboarding_experiment.rb6
-rw-r--r--app/graphql/mutations/user_preferences/update.rb17
-rw-r--r--app/graphql/resolvers/base_issues_resolver.rb10
-rw-r--r--app/graphql/resolvers/concerns/issue_resolver_arguments.rb1
-rw-r--r--app/graphql/types/issue_sort_enum.rb2
-rw-r--r--app/models/issue.rb4
-rw-r--r--config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml8
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/operations/incident_management/incidents.md14
-rw-r--r--doc/operations/incident_management/paging.md7
-rw-r--r--doc/user/project/integrations/webhook_events.md2
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb9
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap37
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap452
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js46
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb44
-rw-r--r--spec/graphql/types/issue_sort_enum_spec.rb2
-rw-r--r--spec/models/issue_spec.rb18
-rw-r--r--spec/requests/api/graphql/mutations/user_preferences/update_spec.rb22
26 files changed, 570 insertions, 282 deletions
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
index 67962d69fa5..db9ef4df8af 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
@@ -127,8 +127,12 @@ export default {
</p>
<gl-progress-bar :value="progressValue" :max="$options.maxValue" />
</div>
- <div class="row row-cols-1 row-cols-md-3 gl-mt-5">
- <div v-for="section in $options.actionSections" :key="section" class="col gl-mb-6">
+ <div class="row">
+ <div
+ v-for="section in $options.actionSections"
+ :key="section"
+ class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
+ >
<learn-gitlab-section-card
:section="section"
:svg="svgFor(section)"
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue
index 6a196687a76..e8f0e6c47ee 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue
@@ -34,17 +34,23 @@ export default {
};
</script>
<template>
- <gl-card class="gl-pt-0 learn-gitlab-section-card">
- <div class="learn-gitlab-section-card-header">
+ <gl-card
+ class="gl-pt-0 h-100"
+ header-class="gl-bg-white gl-border-0 gl-pb-0"
+ body-class="gl-pt-0"
+ >
+ <template #header>
<img :src="svg" />
<h2 class="gl-font-lg gl-mb-3">{{ $options.i18n[section].title }}</h2>
<p class="gl-text-gray-700 gl-mb-6">{{ $options.i18n[section].description }}</p>
- </div>
- <learn-gitlab-section-link
- v-for="[action, value] in sortedActions"
- :key="action"
- :action="action"
- :value="value"
- />
+ </template>
+ <template #default>
+ <learn-gitlab-section-link
+ v-for="[action, value] in sortedActions"
+ :key="action"
+ :action="action"
+ :value="value"
+ />
+ </template>
</gl-card>
</template>
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
index 573f996a254..b391252ce28 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
@@ -1,16 +1,25 @@
<script>
-import { GlLink, GlIcon } from '@gitlab/ui';
+import { GlLink, GlIcon, GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
+import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import { isExperimentVariant } from '~/experimentation/utils';
import eventHub from '~/invite_members/event_hub';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
import { ACTION_LABELS } from '../constants';
export default {
name: 'LearnGitlabSectionLink',
- components: { GlLink, GlIcon },
+ components: {
+ GlLink,
+ GlIcon,
+ GlButton,
+ GitlabExperiment,
+ },
+ directives: {
+ GlTooltip,
+ },
i18n: {
- ACTION_LABELS,
trialOnly: s__('LearnGitlab|Trial only'),
+ watchHow: __('Watch how'),
},
props: {
action: {
@@ -23,6 +32,9 @@ export default {
},
},
computed: {
+ linkTitle() {
+ return ACTION_LABELS[this.action].title;
+ },
trialOnly() {
return ACTION_LABELS[this.action].trialRequired;
},
@@ -34,6 +46,9 @@ export default {
openInNewTab() {
return ACTION_LABELS[this.action]?.openInNewTab === true || this.value.openInNewTab === true;
},
+ linkToVideoTutorial() {
+ return ACTION_LABELS[this.action].videoTutorial;
+ },
},
methods: {
openModal() {
@@ -44,32 +59,54 @@ export default {
</script>
<template>
<div class="gl-mb-4">
- <span v-if="value.completed" class="gl-text-green-500">
- <gl-icon name="check-circle-filled" :size="16" data-testid="completed-icon" />
- {{ $options.i18n.ACTION_LABELS[action].title }}
- </span>
- <gl-link
- v-else-if="showInviteModalLink"
- data-track-action="click_link"
- :data-track-label="$options.i18n.ACTION_LABELS[action].title"
- data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding"
- data-testid="invite-for-help-continuous-onboarding-experiment-link"
- @click="openModal"
- >
- {{ $options.i18n.ACTION_LABELS[action].title }}
- </gl-link>
- <gl-link
- v-else
- :target="openInNewTab ? '_blank' : '_self'"
- :href="value.url"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- :data-track-label="$options.i18n.ACTION_LABELS[action].title"
- >
- {{ $options.i18n.ACTION_LABELS[action].title }}
- </gl-link>
- <span v-if="trialOnly" class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only">
- - {{ $options.i18n.trialOnly }}
- </span>
+ <div v-if="trialOnly" class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only">
+ {{ $options.i18n.trialOnly }}
+ </div>
+ <div class="flex align-items-center">
+ <span v-if="value.completed" class="gl-text-green-500">
+ <gl-icon name="check-circle-filled" :size="16" data-testid="completed-icon" />
+ {{ linkTitle }}
+ </span>
+ <gl-link
+ v-else-if="showInviteModalLink"
+ data-track-action="click_link"
+ :data-track-label="linkTitle"
+ data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding"
+ data-testid="invite-for-help-continuous-onboarding-experiment-link"
+ @click="openModal"
+ >
+ {{ linkTitle }}
+ </gl-link>
+ <gl-link
+ v-else
+ :target="openInNewTab ? '_blank' : '_self'"
+ :href="value.url"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ :data-track-label="linkTitle"
+ >
+ {{ linkTitle }}
+ </gl-link>
+ <gitlab-experiment name="video_tutorials_continuous_onboarding">
+ <template #control></template>
+ <template #candidate>
+ <gl-button
+ v-if="linkToVideoTutorial"
+ v-gl-tooltip
+ category="tertiary"
+ icon="live-preview"
+ :title="$options.i18n.watchHow"
+ :aria-label="$options.i18n.watchHow"
+ :href="linkToVideoTutorial"
+ target="_blank"
+ class="ml-auto"
+ data-testid="video-tutorial-link"
+ data-track-action="click_video_link"
+ :data-track-label="linkTitle"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLab"
+ />
+ </template>
+ </gitlab-experiment>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
index 1887c48dd1b..9ba5e17237a 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
@@ -40,6 +40,7 @@ export const ACTION_LABELS = {
trialRequired: true,
section: 'workspace',
position: 4,
+ videoTutorial: 'https://vimeo.com/670896787',
},
requiredMrApprovalsEnabled: {
title: s__('LearnGitLab|Add merge request approval'),
@@ -48,6 +49,7 @@ export const ACTION_LABELS = {
trialRequired: true,
section: 'workspace',
position: 5,
+ videoTutorial: 'https://vimeo.com/670904904',
},
mergeRequestCreated: {
title: s__('LearnGitLab|Submit a merge request'),
diff --git a/app/assets/stylesheets/page_bundles/learn_gitlab.scss b/app/assets/stylesheets/page_bundles/learn_gitlab.scss
index 10a4a210d41..189aefb330b 100644
--- a/app/assets/stylesheets/page_bundles/learn_gitlab.scss
+++ b/app/assets/stylesheets/page_bundles/learn_gitlab.scss
@@ -1,11 +1,3 @@
.learn-gitlab-info-card-content {
height: 200px;
}
-
-.learn-gitlab-section-card {
- height: 400px;
-}
-
-.learn-gitlab-section-card-header {
- height: 165px;
-}
diff --git a/app/controllers/projects/learn_gitlab_controller.rb b/app/controllers/projects/learn_gitlab_controller.rb
index 177533b89c8..b9f9a1810b7 100644
--- a/app/controllers/projects/learn_gitlab_controller.rb
+++ b/app/controllers/projects/learn_gitlab_controller.rb
@@ -4,6 +4,7 @@ class Projects::LearnGitlabController < Projects::ApplicationController
before_action :authenticate_user!
before_action :check_experiment_enabled?
before_action :enable_invite_for_help_continuous_onboarding_experiment
+ before_action :enable_video_tutorials_continuous_onboarding_experiment
feature_category :users
@@ -24,4 +25,8 @@ class Projects::LearnGitlabController < Projects::ApplicationController
e.publish_to_database
end
end
+
+ def enable_video_tutorials_continuous_onboarding_experiment
+ experiment(:video_tutorials_continuous_onboarding, namespace: project&.namespace).publish
+ end
end
diff --git a/app/experiments/video_tutorials_continuous_onboarding_experiment.rb b/app/experiments/video_tutorials_continuous_onboarding_experiment.rb
new file mode 100644
index 00000000000..3cb676b25f2
--- /dev/null
+++ b/app/experiments/video_tutorials_continuous_onboarding_experiment.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class VideoTutorialsContinuousOnboardingExperiment < ApplicationExperiment
+ control { }
+ candidate { }
+end
diff --git a/app/graphql/mutations/user_preferences/update.rb b/app/graphql/mutations/user_preferences/update.rb
index c92c6d725b7..b71c952b0f2 100644
--- a/app/graphql/mutations/user_preferences/update.rb
+++ b/app/graphql/mutations/user_preferences/update.rb
@@ -14,6 +14,15 @@ module Mutations
null: true,
description: 'User preferences after mutation.'
+ def ready?(**args)
+ if disabled_sort_value?(args)
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ 'Feature flag `incident_escalations` must be enabled to use this sort order.'
+ end
+
+ super
+ end
+
def resolve(**attributes)
user_preferences = current_user.user_preference
user_preferences.update(attributes)
@@ -23,6 +32,14 @@ module Mutations
errors: errors_on_object(user_preferences)
}
end
+
+ private
+
+ def disabled_sort_value?(args)
+ return false unless [:escalation_status_asc, :escalation_status_desc].include?(args[:issues_sort])
+
+ Feature.disabled?(:incident_escalations)
+ end
end
end
end
diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb
index 3e7509b4068..c5b228749de 100644
--- a/app/graphql/resolvers/base_issues_resolver.rb
+++ b/app/graphql/resolvers/base_issues_resolver.rb
@@ -17,7 +17,8 @@ module Resolvers
NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc
popularity_asc popularity_desc
label_priority_asc label_priority_desc
- milestone_due_asc milestone_due_desc].freeze
+ milestone_due_asc milestone_due_desc
+ escalation_status_asc escalation_status_desc].freeze
def continue_issue_resolve(parent, finder, **args)
issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) }
@@ -31,6 +32,13 @@ module Resolvers
end
end
+ def prepare_params(args, parent)
+ return unless [:escalation_status_asc, :escalation_status_desc].include?(args[:sort])
+ return if Feature.enabled?(:incident_escalations, parent)
+
+ args[:sort] = :created_desc # default for sort argument
+ end
+
private
def unconditional_includes
diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
index 38c79ff52ac..432d6f48607 100644
--- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
+++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
@@ -84,6 +84,7 @@ module IssueResolverArguments
prepare_assignee_username_params(args)
prepare_release_tag_params(args)
+ prepare_params(args, parent) if defined?(prepare_params)
finder = IssuesFinder.new(current_user, args)
diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb
index f8825ff6c46..db51e491d4e 100644
--- a/app/graphql/types/issue_sort_enum.rb
+++ b/app/graphql/types/issue_sort_enum.rb
@@ -14,6 +14,8 @@ module Types
value 'TITLE_DESC', 'Title by descending order.', value: :title_desc
value 'POPULARITY_ASC', 'Number of upvotes (awarded "thumbs up" emoji) by ascending order.', value: :popularity_asc
value 'POPULARITY_DESC', 'Number of upvotes (awarded "thumbs up" emoji) by descending order.', value: :popularity_desc
+ value 'ESCALATION_STATUS_ASC', 'Status from triggered to resolved. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled.', value: :escalation_status_asc
+ value 'ESCALATION_STATUS_DESC', 'Status from resolved to triggered. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled.', value: :escalation_status_desc
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 75727fff2cd..91d4b78f7c8 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -125,6 +125,8 @@ class Issue < ApplicationRecord
scope :order_created_at_desc, -> { reorder(created_at: :desc) }
scope :order_severity_asc, -> { includes(:issuable_severity).order('issuable_severities.severity ASC NULLS FIRST') }
scope :order_severity_desc, -> { includes(:issuable_severity).order('issuable_severities.severity DESC NULLS LAST') }
+ scope :order_escalation_status_asc, -> { includes(:incident_management_issuable_escalation_status).order(::Gitlab::Database.nulls_last_order('incident_management_issuable_escalation_status.status')) }
+ scope :order_escalation_status_desc, -> { includes(:incident_management_issuable_escalation_status).order(::Gitlab::Database.nulls_last_order('incident_management_issuable_escalation_status.status', 'DESC')) }
scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) }
scope :with_web_entity_associations, -> { preload(:author, project: [:project_feature, :route, namespace: :route]) }
@@ -327,6 +329,8 @@ class Issue < ApplicationRecord
when 'relative_position', 'relative_position_asc' then order_by_relative_position
when 'severity_asc' then order_severity_asc.with_order_id_desc
when 'severity_desc' then order_severity_desc.with_order_id_desc
+ when 'escalation_status_asc' then order_escalation_status_asc.with_order_id_desc
+ when 'escalation_status_desc' then order_escalation_status_desc.with_order_id_desc
else
super
end
diff --git a/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml b/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml
new file mode 100644
index 00000000000..6dc3f798f63
--- /dev/null
+++ b/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml
@@ -0,0 +1,8 @@
+---
+name: video_tutorials_continuous_onboarding
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82274
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351916
+milestone: '14.9'
+type: experiment
+group: group::adoption
+default_enabled: false
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 434e0c43edf..d1ab2cb0d79 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -18298,6 +18298,8 @@ Values for sorting issues.
| <a id="issuesortcreated_desc"></a>`CREATED_DESC` | Created at descending order. |
| <a id="issuesortdue_date_asc"></a>`DUE_DATE_ASC` | Due date by ascending order. |
| <a id="issuesortdue_date_desc"></a>`DUE_DATE_DESC` | Due date by descending order. |
+| <a id="issuesortescalation_status_asc"></a>`ESCALATION_STATUS_ASC` | Status from triggered to resolved. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled. |
+| <a id="issuesortescalation_status_desc"></a>`ESCALATION_STATUS_DESC` | Status from resolved to triggered. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled. |
| <a id="issuesortlabel_priority_asc"></a>`LABEL_PRIORITY_ASC` | Label priority by ascending order. |
| <a id="issuesortlabel_priority_desc"></a>`LABEL_PRIORITY_DESC` | Label priority by descending order. |
| <a id="issuesortmilestone_due_asc"></a>`MILESTONE_DUE_ASC` | Milestone due date by ascending order. |
diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md
index c7ed386f505..bf20119e20b 100644
--- a/doc/operations/incident_management/incidents.md
+++ b/doc/operations/incident_management/incidents.md
@@ -255,7 +255,12 @@ Add a to-do for incidents that you want to track in your to-do list. Click the
### Change incident status
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9.
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9 [with a flag](../../administration/feature_flags.md) named `incident_escalations`. Disabled by default.
+
+FLAG:
+By default this feature is not available. To make it available per project or for your entire
+instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md)
+named `incident_escalations`.
For users with the Developer role or higher, select **Edit** in the **Status** section of the
right-hand side bar of an incident, then select a status. **Triggered** is the default status for
@@ -273,7 +278,12 @@ updating the incident status also updates the alert status.
## Change escalation policy **(PREMIUM)**
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9.
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9 [with a flag](../../administration/feature_flags.md) named `incident_escalations`. Disabled by default.
+
+FLAG:
+By default this feature is not available. To make it available per project or for your entire
+instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md)
+named `incident_escalations`.
For users with the Developer role or higher, select **Edit** in the **Escalation policy** section of
the right-hand side bar of an incident, then select a policy. By default, new incidents do not have
diff --git a/doc/operations/incident_management/paging.md b/doc/operations/incident_management/paging.md
index 27854a9e201..dd1532e26bf 100644
--- a/doc/operations/incident_management/paging.md
+++ b/doc/operations/incident_management/paging.md
@@ -50,7 +50,12 @@ or stop alert escalations by [updating the alert's status](alerts.md#update-an-a
### Escalating an incident
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9.
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9 [with a flag](../../administration/feature_flags.md) named `incident_escalations`. Disabled by default.
+
+FLAG:
+By default this feature is not available. To make it available per project or for your entire
+instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md)
+named `incident_escalations`.
For incidents, paging on-call responders is optional for each individual incident.
To begin escalating the incident, [set the incident's escalation policy](incidents.md#change-escalation-policy).
diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md
index d37196ec114..53c56a7c9af 100644
--- a/doc/user/project/integrations/webhook_events.md
+++ b/doc/user/project/integrations/webhook_events.md
@@ -203,7 +203,7 @@ The `assignee` and `assignee_id` keys are deprecated
and contain the first assignee only.
The `escalation_status` and `escalation_policy` fields are
-only available for issue types which support escalations,
+only available for issue types which [support escalations](../../../operations/incident_management/paging.md#paging),
such as incidents.
Request header:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c71c614ebda..36f573bb5a3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -41559,6 +41559,9 @@ msgstr ""
msgid "Warning: Synchronizing LDAP removes direct members' access."
msgstr ""
+msgid "Watch how"
+msgstr ""
+
msgid "We are currently unable to fetch data for the pipeline header."
msgstr ""
diff --git a/spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb b/spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb
new file mode 100644
index 00000000000..596791308a4
--- /dev/null
+++ b/spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe VideoTutorialsContinuousOnboardingExperiment do
+ it "defines a control and candidate" do
+ expect(subject.behaviors.keys).to match_array(%w[control candidate])
+ end
+end
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
index 9e00ace761c..83feb621478 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
@@ -2,31 +2,26 @@
exports[`Learn GitLab Section Card renders correctly 1`] = `
<gl-card-stub
- bodyclass=""
- class="gl-pt-0 learn-gitlab-section-card"
+ bodyclass="gl-pt-0"
+ class="gl-pt-0 h-100"
footerclass=""
- headerclass=""
+ headerclass="gl-bg-white gl-border-0 gl-pb-0"
>
- <div
- class="learn-gitlab-section-card-header"
+ <img
+ src="workspace.svg"
+ />
+
+ <h2
+ class="gl-font-lg gl-mb-3"
>
- <img
- src="workspace.svg"
- />
-
- <h2
- class="gl-font-lg gl-mb-3"
- >
- Set up your workspace
- </h2>
-
- <p
- class="gl-text-gray-700 gl-mb-6"
- >
- Complete these tasks first so you can enjoy GitLab's features to their fullest:
- </p>
- </div>
+ Set up your workspace
+ </h2>
+ <p
+ class="gl-text-gray-700 gl-mb-6"
+ >
+ Complete these tasks first so you can enjoy GitLab's features to their fullest:
+ </p>
<learn-gitlab-section-link-stub
action="userAdded"
value="[object Object]"
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
index 62cf769cffd..269c7467c8b 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
@@ -51,170 +51,204 @@ exports[`Learn GitLab renders correctly 1`] = `
</div>
<div
- class="row row-cols-1 row-cols-md-3 gl-mt-5"
+ class="row"
>
<div
- class="col gl-mb-6"
+ class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
>
<div
- class="gl-card gl-pt-0 learn-gitlab-section-card"
+ class="gl-card gl-pt-0 h-100"
>
- <!---->
-
<div
- class="gl-card-body"
+ class="gl-card-header gl-bg-white gl-border-0 gl-pb-0"
>
- <div
- class="learn-gitlab-section-card-header"
+ <img
+ src="workspace.svg"
+ />
+
+ <h2
+ class="gl-font-lg gl-mb-3"
>
- <img
- src="workspace.svg"
- />
-
- <h2
- class="gl-font-lg gl-mb-3"
- >
- Set up your workspace
- </h2>
-
- <p
- class="gl-text-gray-700 gl-mb-6"
- >
- Complete these tasks first so you can enjoy GitLab's features to their fullest:
- </p>
- </div>
+ Set up your workspace
+ </h2>
+ <p
+ class="gl-text-gray-700 gl-mb-6"
+ >
+ Complete these tasks first so you can enjoy GitLab's features to their fullest:
+ </p>
+ </div>
+
+ <div
+ class="gl-card-body gl-pt-0"
+ >
<div
class="gl-mb-4"
>
- <span
- class="gl-text-green-500"
+ <!---->
+
+ <div
+ class="flex align-items-center"
>
- <svg
- aria-hidden="true"
- class="gl-icon s16"
- data-testid="completed-icon"
- role="img"
+ <span
+ class="gl-text-green-500"
>
- <use
- href="#check-circle-filled"
- />
- </svg>
-
- Invite your colleagues
-
- </span>
-
- <!---->
+ <svg
+ aria-hidden="true"
+ class="gl-icon s16"
+ data-testid="completed-icon"
+ role="img"
+ >
+ <use
+ href="#check-circle-filled"
+ />
+ </svg>
+
+ Invite your colleagues
+
+ </span>
+
+ <!---->
+ </div>
</div>
<div
class="gl-mb-4"
>
- <span
- class="gl-text-green-500"
+ <!---->
+
+ <div
+ class="flex align-items-center"
>
- <svg
- aria-hidden="true"
- class="gl-icon s16"
- data-testid="completed-icon"
- role="img"
+ <span
+ class="gl-text-green-500"
>
- <use
- href="#check-circle-filled"
- />
- </svg>
-
- Create or import a repository
-
- </span>
-
- <!---->
+ <svg
+ aria-hidden="true"
+ class="gl-icon s16"
+ data-testid="completed-icon"
+ role="img"
+ >
+ <use
+ href="#check-circle-filled"
+ />
+ </svg>
+
+ Create or import a repository
+
+ </span>
+
+ <!---->
+ </div>
</div>
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Set up CI/CD"
- href="http://example.com/"
- target="_self"
- >
-
- Set up CI/CD
-
- </a>
-
<!---->
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Set up CI/CD"
+ href="http://example.com/"
+ target="_self"
+ >
+
+ Set up CI/CD
+
+ </a>
+
+ <!---->
+ </div>
</div>
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Start a free Ultimate trial"
- href="http://example.com/"
- target="_self"
- >
-
- Start a free Ultimate trial
-
- </a>
-
<!---->
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Start a free Ultimate trial"
+ href="http://example.com/"
+ target="_self"
+ >
+
+ Start a free Ultimate trial
+
+ </a>
+
+ <!---->
+ </div>
</div>
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Add code owners"
- href="http://example.com/"
- target="_self"
- >
-
- Add code owners
-
- </a>
-
- <span
+ <div
class="gl-font-style-italic gl-text-gray-500"
data-testid="trial-only"
>
- - Trial only
+ Trial only
- </span>
+ </div>
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Add code owners"
+ href="http://example.com/"
+ target="_self"
+ >
+
+ Add code owners
+
+ </a>
+
+ <!---->
+ </div>
</div>
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Add merge request approval"
- href="http://example.com/"
- target="_self"
- >
-
- Add merge request approval
-
- </a>
-
- <span
+ <div
class="gl-font-style-italic gl-text-gray-500"
data-testid="trial-only"
>
- - Trial only
+ Trial only
- </span>
+ </div>
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Add merge request approval"
+ href="http://example.com/"
+ target="_self"
+ >
+
+ Add merge request approval
+
+ </a>
+
+ <!---->
+ </div>
</div>
</div>
@@ -222,71 +256,81 @@ exports[`Learn GitLab renders correctly 1`] = `
</div>
</div>
<div
- class="col gl-mb-6"
+ class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
>
<div
- class="gl-card gl-pt-0 learn-gitlab-section-card"
+ class="gl-card gl-pt-0 h-100"
>
- <!---->
-
<div
- class="gl-card-body"
+ class="gl-card-header gl-bg-white gl-border-0 gl-pb-0"
>
- <div
- class="learn-gitlab-section-card-header"
+ <img
+ src="plan.svg"
+ />
+
+ <h2
+ class="gl-font-lg gl-mb-3"
>
- <img
- src="plan.svg"
- />
-
- <h2
- class="gl-font-lg gl-mb-3"
- >
- Plan and execute
- </h2>
-
- <p
- class="gl-text-gray-700 gl-mb-6"
- >
- Create a workflow for your new workspace, and learn how GitLab features work together:
- </p>
- </div>
+ Plan and execute
+ </h2>
+ <p
+ class="gl-text-gray-700 gl-mb-6"
+ >
+ Create a workflow for your new workspace, and learn how GitLab features work together:
+ </p>
+ </div>
+
+ <div
+ class="gl-card-body gl-pt-0"
+ >
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Create an issue"
- href="http://example.com/"
- target="_self"
- >
-
- Create an issue
-
- </a>
-
<!---->
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Create an issue"
+ href="http://example.com/"
+ target="_self"
+ >
+
+ Create an issue
+
+ </a>
+
+ <!---->
+ </div>
</div>
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Submit a merge request"
- href="http://example.com/"
- target="_self"
- >
-
- Submit a merge request
-
- </a>
-
<!---->
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Submit a merge request"
+ href="http://example.com/"
+ target="_self"
+ >
+
+ Submit a merge request
+
+ </a>
+
+ <!---->
+ </div>
</div>
</div>
@@ -294,54 +338,58 @@ exports[`Learn GitLab renders correctly 1`] = `
</div>
</div>
<div
- class="col gl-mb-6"
+ class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
>
<div
- class="gl-card gl-pt-0 learn-gitlab-section-card"
+ class="gl-card gl-pt-0 h-100"
>
- <!---->
-
<div
- class="gl-card-body"
+ class="gl-card-header gl-bg-white gl-border-0 gl-pb-0"
>
- <div
- class="learn-gitlab-section-card-header"
+ <img
+ src="deploy.svg"
+ />
+
+ <h2
+ class="gl-font-lg gl-mb-3"
>
- <img
- src="deploy.svg"
- />
-
- <h2
- class="gl-font-lg gl-mb-3"
- >
- Deploy
- </h2>
-
- <p
- class="gl-text-gray-700 gl-mb-6"
- >
- Use your new GitLab workflow to deploy your application, monitor its health, and keep it secure:
- </p>
- </div>
+ Deploy
+ </h2>
+ <p
+ class="gl-text-gray-700 gl-mb-6"
+ >
+ Use your new GitLab workflow to deploy your application, monitor its health, and keep it secure:
+ </p>
+ </div>
+
+ <div
+ class="gl-card-body gl-pt-0"
+ >
<div
class="gl-mb-4"
>
- <a
- class="gl-link"
- data-testid="uncompleted-learn-gitlab-link"
- data-track-action="click_link"
- data-track-label="Run a Security scan using CI/CD"
- href="https://docs.gitlab.com/ee/foobar/"
- rel="noopener noreferrer"
- target="_blank"
- >
-
- Run a Security scan using CI/CD
-
- </a>
-
<!---->
+
+ <div
+ class="flex align-items-center"
+ >
+ <a
+ class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
+ data-track-action="click_link"
+ data-track-label="Run a Security scan using CI/CD"
+ href="https://docs.gitlab.com/ee/foobar/"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+
+ Run a Security scan using CI/CD
+
+ </a>
+
+ <!---->
+ </div>
</div>
</div>
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
index e21371123e8..551c68a99ab 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { stubExperiments } from 'helpers/experimentation_helper';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import eventHub from '~/invite_members/event_hub';
@@ -26,7 +26,7 @@ describe('Learn GitLab Section Link', () => {
});
const createWrapper = (action = defaultAction, props = {}) => {
- wrapper = shallowMount(LearnGitlabSectionLink, {
+ wrapper = mount(LearnGitlabSectionLink, {
propsData: { action, value: { ...defaultProps, ...props } },
});
};
@@ -36,6 +36,8 @@ describe('Learn GitLab Section Link', () => {
const findUncompletedLink = () => wrapper.find('[data-testid="uncompleted-learn-gitlab-link"]');
+ const videoTutorialLink = () => wrapper.find('[data-testid="video-tutorial-link"]');
+
it('renders no icon when not completed', () => {
createWrapper(undefined, { completed: false });
@@ -130,4 +132,44 @@ describe('Learn GitLab Section Link', () => {
unmockTracking();
});
});
+
+ describe('video_tutorials_continuous_onboarding experiment', () => {
+ describe('when control', () => {
+ beforeEach(() => {
+ stubExperiments({ video_tutorials_continuous_onboarding: 'control' });
+ createWrapper('codeOwnersEnabled');
+ });
+
+ it('renders no video link', () => {
+ expect(videoTutorialLink().exists()).toBe(false);
+ });
+ });
+
+ describe('when candidate', () => {
+ beforeEach(() => {
+ stubExperiments({ video_tutorials_continuous_onboarding: 'candidate' });
+ createWrapper('codeOwnersEnabled');
+ });
+
+ it('renders video link with blank target', () => {
+ const videoLinkElement = videoTutorialLink();
+
+ expect(videoLinkElement.exists()).toBe(true);
+ expect(videoLinkElement.attributes('target')).toEqual('_blank');
+ });
+
+ it('tracks the click', () => {
+ const trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+
+ videoTutorialLink().trigger('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_video_link', {
+ label: 'Add code owners',
+ property: 'Growth::Conversion::Experiment::LearnGitLab',
+ });
+
+ unmockTracking();
+ });
+ });
+ });
});
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 5e9a3d0a68b..81aeee0a3d2 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -522,11 +522,53 @@ RSpec.describe Resolvers::IssuesResolver do
end
end
+ context 'when sorting by escalation status' do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:triggered_incident) { create(:incident, :with_escalation_status, project: project) }
+ let_it_be(:issue_no_status) { create(:issue, project: project) }
+ let_it_be(:resolved_incident) do
+ create(:incident, :with_escalation_status, project: project)
+ .tap { |issue| issue.escalation_status.resolve }
+ end
+
+ it 'sorts issues ascending' do
+ issues = resolve_issues(sort: :escalation_status_asc).to_a
+ expect(issues).to eq([triggered_incident, resolved_incident, issue_no_status])
+ end
+
+ it 'sorts issues descending' do
+ issues = resolve_issues(sort: :escalation_status_desc).to_a
+ expect(issues).to eq([resolved_incident, triggered_incident, issue_no_status])
+ end
+
+ it 'sorts issues created_at' do
+ issues = resolve_issues(sort: :created_desc).to_a
+ expect(issues).to eq([resolved_incident, issue_no_status, triggered_incident])
+ end
+
+ context 'when incident_escalations feature flag is disabled' do
+ before do
+ stub_feature_flags(incident_escalations: false)
+ end
+
+ it 'defaults ascending status sort to created_desc' do
+ issues = resolve_issues(sort: :escalation_status_asc).to_a
+ expect(issues).to eq([resolved_incident, issue_no_status, triggered_incident])
+ end
+
+ it 'defaults descending status sort to created_desc' do
+ issues = resolve_issues(sort: :escalation_status_desc).to_a
+ expect(issues).to eq([resolved_incident, issue_no_status, triggered_incident])
+ end
+ end
+ end
+
context 'when sorting with non-stable cursors' do
%i[priority_asc priority_desc
popularity_asc popularity_desc
label_priority_asc label_priority_desc
- milestone_due_asc milestone_due_desc].each do |sort_by|
+ milestone_due_asc milestone_due_desc
+ escalation_status_asc escalation_status_desc].each do |sort_by|
it "uses offset-pagination when sorting by #{sort_by}" do
resolved = resolve_issues(sort: sort_by)
diff --git a/spec/graphql/types/issue_sort_enum_spec.rb b/spec/graphql/types/issue_sort_enum_spec.rb
index 4433709d193..95184477e75 100644
--- a/spec/graphql/types/issue_sort_enum_spec.rb
+++ b/spec/graphql/types/issue_sort_enum_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['IssueSort'] do
it 'exposes all the existing issue sort values' do
expect(described_class.values.keys).to include(
- *%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC SEVERITY_ASC SEVERITY_DESC]
+ *%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC SEVERITY_ASC SEVERITY_DESC ESCALATION_STATUS_ASC ESCALATION_STATUS_DESC]
)
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 29305ba435c..61ad9dc26be 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -238,6 +238,24 @@ RSpec.describe Issue do
end
end
+ context 'order by escalation status' do
+ let_it_be(:triggered_incident) { create(:incident_management_issuable_escalation_status, :triggered).issue }
+ let_it_be(:resolved_incident) { create(:incident_management_issuable_escalation_status, :resolved).issue }
+ let_it_be(:issue_no_status) { create(:issue) }
+
+ describe '.order_escalation_status_asc' do
+ subject { described_class.order_escalation_status_asc }
+
+ it { is_expected.to eq([triggered_incident, resolved_incident, issue_no_status]) }
+ end
+
+ describe '.order_escalation_status_desc' do
+ subject { described_class.order_escalation_status_desc }
+
+ it { is_expected.to eq([resolved_incident, triggered_incident, issue_no_status]) }
+ end
+ end
+
# TODO: Remove when NOT NULL constraint is added to the relationship
describe '#work_item_type' do
let(:issue) { create(:issue, :incident, project: reusable_project, work_item_type: nil) }
diff --git a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb
index e1c7fd9d60d..85194e6eb20 100644
--- a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb
@@ -28,6 +28,17 @@ RSpec.describe Mutations::UserPreferences::Update do
expect(current_user.user_preference.persisted?).to eq(true)
expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s)
end
+
+ context 'when incident_escalations feature flag is disabled' do
+ let(:sort_value) { 'ESCALATION_STATUS_ASC' }
+
+ before do
+ stub_feature_flags(incident_escalations: false)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Feature flag `incident_escalations` must be enabled to use this sort order.']
+ end
end
context 'when user has existing preference' do
@@ -45,5 +56,16 @@ RSpec.describe Mutations::UserPreferences::Update do
expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s)
end
+
+ context 'when incident_escalations feature flag is disabled' do
+ let(:sort_value) { 'ESCALATION_STATUS_DESC' }
+
+ before do
+ stub_feature_flags(incident_escalations: false)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Feature flag `incident_escalations` must be enabled to use this sort order.']
+ end
end
end