Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7cf8c080ed
commit
412fe7ab55
|
|
@ -602,6 +602,8 @@
|
|||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-pipeline-expedite
|
||||
when: never
|
||||
|
||||
.rails:rules:predictive-default-rules:
|
||||
rules:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Gitlab/DocUrl:
|
|||
- 'app/graphql/types/merge_request_type.rb'
|
||||
- 'app/graphql/types/notes/diff_position_input_type.rb'
|
||||
- 'app/graphql/types/query_complexity_type.rb'
|
||||
- 'app/helpers/learn_gitlab_helper.rb'
|
||||
- 'ee/app/helpers/projects/learn_gitlab_helper.rb'
|
||||
- 'app/models/integrations/apple_app_store.rb'
|
||||
- 'app/models/integrations/microsoft_teams.rb'
|
||||
- 'app/presenters/dev_ops_report/metric_presenter.rb'
|
||||
|
|
|
|||
|
|
@ -726,6 +726,5 @@ Gitlab/StrongMemoizeAttr:
|
|||
- 'lib/sidebars/groups/menus/merge_requests_menu.rb'
|
||||
- 'lib/sidebars/projects/menus/analytics_menu.rb'
|
||||
- 'lib/sidebars/projects/menus/issues_menu.rb'
|
||||
- 'lib/sidebars/projects/menus/learn_gitlab_menu.rb'
|
||||
- 'lib/unnested_in_filters/rewriter.rb'
|
||||
- 'tooling/graphql/docs/helper.rb'
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ Layout/FirstHashElementIndentation:
|
|||
- 'spec/frontend/fixtures/autocomplete_sources.rb'
|
||||
- 'spec/graphql/types/ci/detailed_status_type_spec.rb'
|
||||
- 'spec/helpers/groups/observability_helper_spec.rb'
|
||||
- 'spec/helpers/learn_gitlab_helper_spec.rb'
|
||||
- 'spec/helpers/projects/pages_helper_spec.rb'
|
||||
- 'spec/helpers/routing/pseudonymization_helper_spec.rb'
|
||||
- 'spec/initializers/rack_multipart_patch_spec.rb'
|
||||
|
|
|
|||
|
|
@ -4041,7 +4041,6 @@ Layout/LineLength:
|
|||
- 'spec/helpers/groups/group_members_helper_spec.rb'
|
||||
- 'spec/helpers/groups_helper_spec.rb'
|
||||
- 'spec/helpers/icons_helper_spec.rb'
|
||||
- 'spec/helpers/invite_members_helper_spec.rb'
|
||||
- 'spec/helpers/issuables_helper_spec.rb'
|
||||
- 'spec/helpers/issues_helper_spec.rb'
|
||||
- 'spec/helpers/labels_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1525,7 +1525,6 @@ RSpec/ContextWording:
|
|||
- 'spec/helpers/groups_helper_spec.rb'
|
||||
- 'spec/helpers/ide_helper_spec.rb'
|
||||
- 'spec/helpers/integrations_helper_spec.rb'
|
||||
- 'spec/helpers/invite_members_helper_spec.rb'
|
||||
- 'spec/helpers/jira_connect_helper_spec.rb'
|
||||
- 'spec/helpers/labels_helper_spec.rb'
|
||||
- 'spec/helpers/listbox_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -163,7 +163,6 @@ RSpec/ExpectInHook:
|
|||
- 'spec/graphql/mutations/design_management/move_spec.rb'
|
||||
- 'spec/helpers/commits_helper_spec.rb'
|
||||
- 'spec/helpers/groups_helper_spec.rb'
|
||||
- 'spec/helpers/invite_members_helper_spec.rb'
|
||||
- 'spec/helpers/projects_helper_spec.rb'
|
||||
- 'spec/helpers/search_helper_spec.rb'
|
||||
- 'spec/helpers/users_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ RSpec/FactoryBot/AvoidCreate:
|
|||
- 'ee/spec/helpers/ee/issuables_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/issues_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/labels_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/learn_gitlab_helper_spec.rb'
|
||||
- 'ee/spec/helpers/projects/learn_gitlab_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/lock_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/namespaces_helper_spec.rb'
|
||||
|
|
@ -305,7 +305,6 @@ RSpec/FactoryBot/AvoidCreate:
|
|||
- '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/markup_helper_spec.rb'
|
||||
- 'spec/helpers/members_helper_spec.rb'
|
||||
- 'spec/helpers/merge_requests_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -584,7 +584,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'ee/spec/helpers/ee/invite_members_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/issues_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/labels_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/learn_gitlab_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/lock_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/namespaces_helper_spec.rb'
|
||||
|
|
@ -2583,7 +2582,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'ee/spec/workers/merge_trains/refresh_worker_spec.rb'
|
||||
- 'ee/spec/workers/namespaces/sync_namespace_name_worker_spec.rb'
|
||||
- 'ee/spec/workers/new_epic_worker_spec.rb'
|
||||
- 'ee/spec/workers/onboarding/create_learn_gitlab_worker_spec.rb'
|
||||
- 'ee/spec/workers/personal_access_tokens/groups/policy_worker_spec.rb'
|
||||
- 'ee/spec/workers/personal_access_tokens/instance/policy_worker_spec.rb'
|
||||
- 'ee/spec/workers/post_receive_spec.rb'
|
||||
|
|
@ -6051,7 +6049,6 @@ RSpec/MissingFeatureCategory:
|
|||
- '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'
|
||||
|
|
@ -6555,7 +6552,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/models/oauth_access_grant_spec.rb'
|
||||
- 'spec/models/oauth_access_token_spec.rb'
|
||||
- 'spec/models/onboarding/completion_spec.rb'
|
||||
- 'spec/models/onboarding/learn_gitlab_spec.rb'
|
||||
- 'spec/models/onboarding/progress_spec.rb'
|
||||
- 'spec/models/operations/feature_flag_spec.rb'
|
||||
- 'spec/models/operations/feature_flags/strategy_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1065,7 +1065,6 @@ Style/IfUnlessModifier:
|
|||
- 'spec/features/projects/tree/create_file_spec.rb'
|
||||
- 'spec/graphql/mutations/releases/update_spec.rb'
|
||||
- 'spec/helpers/application_settings_helper_spec.rb'
|
||||
- 'spec/helpers/invite_members_helper_spec.rb'
|
||||
- 'spec/lib/container_registry/gitlab_api_client_spec.rb'
|
||||
- 'spec/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images_spec.rb'
|
||||
- 'spec/lib/gitlab/config/entry/validators/nested_array_helpers_spec.rb'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -504,7 +504,7 @@ gem 'ssh_data', '~> 1.3'
|
|||
gem 'spamcheck', '~> 1.0.0'
|
||||
|
||||
# Gitaly GRPC protocol definitions
|
||||
gem 'gitaly', '~> 15.5.2'
|
||||
gem 'gitaly', '~> 15.8.0-rc1'
|
||||
|
||||
# KAS GRPC protocol definitions
|
||||
gem 'kas-grpc', '~> 0.0.2'
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@
|
|||
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
|
||||
{"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
|
||||
{"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"},
|
||||
{"name":"gitaly","version":"15.5.2","platform":"ruby","checksum":"62babe0596a4505bf95051ea50f17160055e6cf6cacf209273691542120d7881"},
|
||||
{"name":"gitaly","version":"15.8.0.pre.rc1","platform":"ruby","checksum":"9244245b602c6c903eb0e3b3629b51e888af179cbbe339269095a1ab9113dbb5"},
|
||||
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
|
||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||
{"name":"gitlab-dangerfiles","version":"3.6.6","platform":"ruby","checksum":"cabfe23490120188a653c827a32121bdd4abf4e9e91d1754bf170dd7e93781f1"},
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ GEM
|
|||
rails (>= 3.2.0)
|
||||
git (1.11.0)
|
||||
rchardet (~> 1.8)
|
||||
gitaly (15.5.2)
|
||||
gitaly (15.8.0.pre.rc1)
|
||||
grpc (~> 1.0)
|
||||
gitlab (4.19.0)
|
||||
httparty (~> 0.20)
|
||||
|
|
@ -1667,7 +1667,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.3)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly (~> 15.5.2)
|
||||
gitaly (~> 15.8.0.pre.rc1)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 3.6.6)
|
||||
gitlab-experiment (~> 0.7.1)
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'IncludedInTrialIndicator',
|
||||
i18n: {
|
||||
trialOnly: s__('LearnGitlab|- Included in trial'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only">
|
||||
{{ $options.i18n.trialOnly }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
<script>
|
||||
import { GlProgressBar, GlSprintf, GlAlert } from '@gitlab/ui';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import { s__ } from '~/locale';
|
||||
import { getCookie, removeCookie, parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { ACTION_LABELS, ACTION_SECTIONS, INVITE_MODAL_OPEN_COOKIE } from '../constants';
|
||||
import LearnGitlabSectionCard from './learn_gitlab_section_card.vue';
|
||||
|
||||
export default {
|
||||
components: { GlProgressBar, GlSprintf, GlAlert, LearnGitlabSectionCard },
|
||||
i18n: {
|
||||
title: s__('LearnGitLab|Learn GitLab'),
|
||||
description: s__(
|
||||
'LearnGitLab|Ready to get started with GitLab? Follow these steps to set up your workspace, plan and commit changes, and deploy your project.',
|
||||
),
|
||||
percentageCompleted: s__(`LearnGitLab|%{percentage}%{percentSymbol} completed`),
|
||||
successfulInvitations: s__(
|
||||
"LearnGitLab|Your team is growing! You've successfully invited new team members to the %{projectName} project.",
|
||||
),
|
||||
},
|
||||
props: {
|
||||
actions: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
sections: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
project: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSuccessfulInvitationsAlert: false,
|
||||
actionsData: this.actions,
|
||||
};
|
||||
},
|
||||
actionSections: Object.keys(ACTION_SECTIONS),
|
||||
computed: {
|
||||
maxValue() {
|
||||
return Object.keys(this.actionsData).length;
|
||||
},
|
||||
progressValue() {
|
||||
return Object.values(this.actionsData).filter((a) => a.completed).length;
|
||||
},
|
||||
progressPercentage() {
|
||||
return Math.round((this.progressValue / this.maxValue) * 100);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.getCookieForInviteMembers()) {
|
||||
this.openInviteMembersModal('celebrate');
|
||||
}
|
||||
|
||||
eventHub.$on('showSuccessfulInvitationsAlert', this.handleShowSuccessfulInvitationsAlert);
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('showSuccessfulInvitationsAlert', this.handleShowSuccessfulInvitationsAlert);
|
||||
},
|
||||
methods: {
|
||||
getCookieForInviteMembers() {
|
||||
const value = parseBoolean(getCookie(INVITE_MODAL_OPEN_COOKIE));
|
||||
|
||||
removeCookie(INVITE_MODAL_OPEN_COOKIE);
|
||||
|
||||
return value;
|
||||
},
|
||||
openInviteMembersModal(mode) {
|
||||
eventHub.$emit('openModal', { mode, source: 'learn-gitlab' });
|
||||
},
|
||||
handleShowSuccessfulInvitationsAlert() {
|
||||
this.showSuccessfulInvitationsAlert = true;
|
||||
this.markActionAsCompleted('userAdded');
|
||||
},
|
||||
actionsFor(section) {
|
||||
const actions = Object.fromEntries(
|
||||
Object.entries(this.actionsData).filter(
|
||||
([action]) => ACTION_LABELS[action].section === section,
|
||||
),
|
||||
);
|
||||
return actions;
|
||||
},
|
||||
svgFor(section) {
|
||||
return this.sections[section].svg;
|
||||
},
|
||||
markActionAsCompleted(completedAction) {
|
||||
Object.keys(this.actionsData).forEach((action) => {
|
||||
if (action === completedAction) {
|
||||
this.actionsData[action].completed = true;
|
||||
this.modifySidebarPercentage();
|
||||
}
|
||||
});
|
||||
},
|
||||
modifySidebarPercentage() {
|
||||
const el = document.querySelector('.sidebar-top-level-items .active .count');
|
||||
el.textContent = `${this.progressPercentage}%`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-alert
|
||||
v-if="showSuccessfulInvitationsAlert"
|
||||
class="gl-mt-5"
|
||||
@dismiss="showSuccessfulInvitationsAlert = false"
|
||||
>
|
||||
<gl-sprintf :message="$options.i18n.successfulInvitations">
|
||||
<template #projectName>
|
||||
<strong>{{ project.name }}</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
<div class="row">
|
||||
<div class="gl-mb-7 gl-ml-5">
|
||||
<h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
|
||||
<p class="gl-text-gray-700 gl-mb-0">{{ $options.i18n.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gl-mb-3">
|
||||
<p class="gl-text-gray-500 gl-mb-2" data-testid="completion-percentage">
|
||||
<gl-sprintf :message="$options.i18n.percentageCompleted">
|
||||
<template #percentage>{{ progressPercentage }}</template>
|
||||
<template #percentSymbol>%</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<gl-progress-bar :value="progressValue" :max="maxValue" />
|
||||
</div>
|
||||
<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)"
|
||||
:actions="actionsFor(section)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<script>
|
||||
import { GlCard } from '@gitlab/ui';
|
||||
import { ACTION_LABELS, ACTION_SECTIONS } from '../constants';
|
||||
|
||||
import LearnGitlabSectionLink from './learn_gitlab_section_link.vue';
|
||||
|
||||
export default {
|
||||
name: 'LearnGitlabSectionCard',
|
||||
components: { GlCard, LearnGitlabSectionLink },
|
||||
i18n: {
|
||||
...ACTION_SECTIONS,
|
||||
},
|
||||
props: {
|
||||
section: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
svg: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
actions: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sortedActions() {
|
||||
return Object.entries(this.actions).sort(
|
||||
(a1, a2) => ACTION_LABELS[a1[0]].position - ACTION_LABELS[a2[0]].position,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
<template #default>
|
||||
<learn-gitlab-section-link
|
||||
v-for="[action, value] in sortedActions"
|
||||
:key="action"
|
||||
:action="action"
|
||||
:value="value"
|
||||
/>
|
||||
</template>
|
||||
</gl-card>
|
||||
</template>
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
<script>
|
||||
import { uniqueId } from 'lodash';
|
||||
import { GlLink, GlIcon, GlButton, GlPopover, 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 { ACTION_LABELS } from '../constants';
|
||||
import IncludedInTrialIndicator from './included_in_trial_indicator.vue';
|
||||
|
||||
export default {
|
||||
name: 'LearnGitlabSectionLink',
|
||||
components: {
|
||||
GlLink,
|
||||
GlIcon,
|
||||
GlButton,
|
||||
GlPopover,
|
||||
GitlabExperiment,
|
||||
IncludedInTrialIndicator,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip,
|
||||
},
|
||||
i18n: {
|
||||
contactAdmin: s__('LearnGitlab|Contact your administrator to enable this action.'),
|
||||
viewAdminList: s__('LearnGitlab|View administrator list'),
|
||||
watchHow: __('Watch how'),
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popoverId: uniqueId('contact-admin-'),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showInviteModalLink() {
|
||||
return (
|
||||
this.action === 'userAdded' && isExperimentVariant('invite_for_help_continuous_onboarding')
|
||||
);
|
||||
},
|
||||
openInNewTab() {
|
||||
return ACTION_LABELS[this.action]?.openInNewTab === true || this.value.openInNewTab === true;
|
||||
},
|
||||
popoverText() {
|
||||
return this.value.message || this.$options.i18n.contactAdmin;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
eventHub.$emit('openModal', { source: 'learn_gitlab' });
|
||||
},
|
||||
actionLabelValue(value) {
|
||||
return ACTION_LABELS[this.action][value];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="gl-mb-4">
|
||||
<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" />
|
||||
{{ actionLabelValue('title') }}
|
||||
<included-in-trial-indicator v-if="actionLabelValue('trialRequired')" />
|
||||
</span>
|
||||
<div v-else-if="showInviteModalLink">
|
||||
<gl-link
|
||||
data-track-action="click_link"
|
||||
:data-track-label="actionLabelValue('trackLabel')"
|
||||
data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding"
|
||||
data-testid="invite-for-help-continuous-onboarding-experiment-link"
|
||||
@click="openModal"
|
||||
>{{ actionLabelValue('title') }}</gl-link
|
||||
>
|
||||
|
||||
<included-in-trial-indicator v-if="actionLabelValue('trialRequired')" />
|
||||
</div>
|
||||
<div v-else-if="value.enabled">
|
||||
<gl-link
|
||||
:target="openInNewTab ? '_blank' : '_self'"
|
||||
:href="value.url"
|
||||
data-testid="uncompleted-learn-gitlab-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_link"
|
||||
data-track-action="click_link"
|
||||
:data-track-label="actionLabelValue('trackLabel')"
|
||||
>{{ actionLabelValue('title') }}</gl-link
|
||||
>
|
||||
|
||||
<included-in-trial-indicator v-if="actionLabelValue('trialRequired')" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div data-testid="disabled-learn-gitlab-link">{{ actionLabelValue('title') }}</div>
|
||||
<gl-button
|
||||
:id="popoverId"
|
||||
category="tertiary"
|
||||
icon="question-o"
|
||||
class="ml-auto"
|
||||
:aria-label="popoverText"
|
||||
size="small"
|
||||
data-testid="contact-admin-popover-trigger"
|
||||
/>
|
||||
<gl-popover
|
||||
:target="popoverId"
|
||||
placement="top"
|
||||
triggers="hover focus"
|
||||
data-testid="contact-admin-popover"
|
||||
>
|
||||
<p>{{ popoverText }}</p>
|
||||
<gl-link
|
||||
:href="value.url"
|
||||
class="font-size-inherit"
|
||||
data-testid="view-administrator-link-text"
|
||||
>
|
||||
{{ $options.i18n.viewAdminList }}
|
||||
</gl-link>
|
||||
</gl-popover>
|
||||
</template>
|
||||
<gitlab-experiment name="video_tutorials_continuous_onboarding">
|
||||
<template #control></template>
|
||||
<template #candidate>
|
||||
<gl-button
|
||||
v-if="actionLabelValue('videoTutorial')"
|
||||
v-gl-tooltip
|
||||
category="tertiary"
|
||||
icon="live-preview"
|
||||
:title="$options.i18n.watchHow"
|
||||
:aria-label="$options.i18n.watchHow"
|
||||
:href="actionLabelValue('videoTutorial')"
|
||||
target="_blank"
|
||||
class="ml-auto"
|
||||
size="small"
|
||||
data-testid="video-tutorial-link"
|
||||
data-track-action="click_video_link"
|
||||
:data-track-label="actionLabelValue('trackLabel')"
|
||||
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
|
||||
data-track-experiment="video_tutorials_continuous_onboarding"
|
||||
/>
|
||||
</template>
|
||||
</gitlab-experiment>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
export const ACTION_LABELS = {
|
||||
gitWrite: {
|
||||
title: s__('LearnGitLab|Create a repository'),
|
||||
actionLabel: s__('LearnGitLab|Create a repository'),
|
||||
description: s__('LearnGitLab|Create or import your first repository into your new project.'),
|
||||
trackLabel: 'create_a_repository',
|
||||
section: 'workspace',
|
||||
position: 1,
|
||||
},
|
||||
userAdded: {
|
||||
title: s__('LearnGitLab|Invite your colleagues'),
|
||||
actionLabel: s__('LearnGitLab|Invite your colleagues'),
|
||||
description: s__(
|
||||
'LearnGitLab|GitLab works best as a team. Invite your colleague to enjoy all features.',
|
||||
),
|
||||
trackLabel: 'invite_your_colleagues',
|
||||
section: 'workspace',
|
||||
position: 0,
|
||||
},
|
||||
pipelineCreated: {
|
||||
title: s__("LearnGitLab|Set up your first project's CI/CD"),
|
||||
actionLabel: s__('LearnGitLab|Set up CI/CD'),
|
||||
description: s__('LearnGitLab|Save time by automating your integration and deployment tasks.'),
|
||||
trackLabel: 'set_up_your_first_project_s_ci_cd',
|
||||
section: 'workspace',
|
||||
position: 2,
|
||||
},
|
||||
trialStarted: {
|
||||
title: s__('LearnGitLab|Start a free trial of GitLab Ultimate'),
|
||||
actionLabel: s__('LearnGitLab|Try GitLab Ultimate for free'),
|
||||
description: s__('LearnGitLab|Try all GitLab features for 30 days, no credit card required.'),
|
||||
trackLabel: 'start_a_free_trial_of_gitlab_ultimate',
|
||||
section: 'workspace',
|
||||
position: 3,
|
||||
openInNewTab: true,
|
||||
},
|
||||
codeOwnersEnabled: {
|
||||
title: s__('LearnGitLab|Add code owners'),
|
||||
actionLabel: s__('LearnGitLab|Add code owners'),
|
||||
description: s__(
|
||||
'LearnGitLab|Prevent unexpected changes to important assets by assigning ownership of files and paths.',
|
||||
),
|
||||
trackLabel: 'add_code_owners',
|
||||
trialRequired: true,
|
||||
section: 'workspace',
|
||||
position: 4,
|
||||
openInNewTab: true,
|
||||
videoTutorial: 'https://vimeo.com/670896787',
|
||||
},
|
||||
requiredMrApprovalsEnabled: {
|
||||
title: s__('LearnGitLab|Enable require merge approvals'),
|
||||
actionLabel: s__('LearnGitLab|Enable require merge approvals'),
|
||||
description: s__('LearnGitLab|Route code reviews to the right reviewers, every time.'),
|
||||
trackLabel: 'enable_require_merge_approvals',
|
||||
trialRequired: true,
|
||||
section: 'workspace',
|
||||
position: 5,
|
||||
openInNewTab: true,
|
||||
videoTutorial: 'https://vimeo.com/670904904',
|
||||
},
|
||||
mergeRequestCreated: {
|
||||
title: s__('LearnGitLab|Submit a merge request (MR)'),
|
||||
actionLabel: s__('LearnGitLab|Submit a merge request (MR)'),
|
||||
description: s__('LearnGitLab|Review and edit proposed changes to source code.'),
|
||||
trackLabel: 'submit_a_merge_request_mr',
|
||||
section: 'plan',
|
||||
position: 1,
|
||||
},
|
||||
issueCreated: {
|
||||
title: s__('LearnGitLab|Create an issue'),
|
||||
actionLabel: s__('LearnGitLab|Create an issue'),
|
||||
description: s__(
|
||||
'LearnGitLab|Create/import issues (tickets) to collaborate on ideas and plan work.',
|
||||
),
|
||||
trackLabel: 'create_an_issue',
|
||||
section: 'plan',
|
||||
position: 0,
|
||||
},
|
||||
securityScanEnabled: {
|
||||
title: s__('LearnGitLab|Run a Security scan using CI/CD'),
|
||||
actionLabel: s__('LearnGitLab|Run a Security scan using CI/CD'),
|
||||
description: s__('LearnGitLab|Scan your code to uncover vulnerabilities before deploying.'),
|
||||
trackLabel: 'run_a_security_scan_using_ci_cd',
|
||||
section: 'deploy',
|
||||
position: 1,
|
||||
},
|
||||
licenseScanningRun: {
|
||||
title: s__('LearnGitLab|Scan dependencies for licenses'),
|
||||
trackLabel: 'scan_dependencies_for_licenses',
|
||||
trialRequired: true,
|
||||
section: 'deploy',
|
||||
position: 2,
|
||||
},
|
||||
secureDependencyScanningRun: {
|
||||
title: s__('LearnGitLab|Scan dependencies for vulnerabilities'),
|
||||
trackLabel: 'scan_dependencies_for_vulnerabilities',
|
||||
trialRequired: true,
|
||||
section: 'deploy',
|
||||
position: 3,
|
||||
},
|
||||
secureDastRun: {
|
||||
title: s__('LearnGitLab|Analyze your application for vulnerabilities with DAST'),
|
||||
trackLabel: 'analyze_your_application_for_vulnerabilities_with_dast',
|
||||
trialRequired: true,
|
||||
section: 'deploy',
|
||||
position: 4,
|
||||
},
|
||||
};
|
||||
|
||||
export const ACTION_SECTIONS = {
|
||||
workspace: {
|
||||
title: s__('LearnGitLab|Set up your workspace'),
|
||||
description: s__(
|
||||
"LearnGitLab|Complete these tasks first so you can enjoy GitLab's features to their fullest:",
|
||||
),
|
||||
},
|
||||
plan: {
|
||||
title: s__('LearnGitLab|Plan and execute'),
|
||||
description: s__(
|
||||
'LearnGitLab|Create a workflow for your new workspace, and learn how GitLab features work together:',
|
||||
),
|
||||
},
|
||||
deploy: {
|
||||
title: s__('LearnGitLab|Deploy'),
|
||||
description: s__(
|
||||
'LearnGitLab|Use your new GitLab workflow to deploy your application, monitor its health, and keep it secure:',
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const INVITE_MODAL_OPEN_COOKIE = 'confetti_post_signup';
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
|
||||
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import LearnGitlab from '../components/learn_gitlab.vue';
|
||||
|
||||
function initLearnGitlab() {
|
||||
const el = document.getElementById('js-learn-gitlab-app');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions));
|
||||
const sections = convertObjectPropsToCamelCase(JSON.parse(el.dataset.sections));
|
||||
const project = convertObjectPropsToCamelCase(JSON.parse(el.dataset.project));
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
return createElement(LearnGitlab, {
|
||||
props: { actions, sections, project },
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
initInviteMembersModal();
|
||||
initInviteMembersTrigger();
|
||||
|
||||
initLearnGitlab();
|
||||
|
|
@ -203,7 +203,7 @@ export default {
|
|||
:is="linkComponent"
|
||||
ref="link"
|
||||
v-gl-hover-load="handlePreload"
|
||||
v-gl-tooltip:tooltip-container
|
||||
v-gl-tooltip="{ placement: 'left', boundary: 'viewport' }"
|
||||
:title="fullPath"
|
||||
:to="routerLinkTo"
|
||||
:href="url"
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
class LearnGitlabController < Projects::ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :verify_learn_gitlab_available!
|
||||
before_action :enable_invite_for_help_continuous_onboarding_experiment
|
||||
before_action :enable_video_tutorials_continuous_onboarding_experiment
|
||||
|
||||
feature_category :user_profile
|
||||
urgency :low, [:index]
|
||||
|
||||
def index; end
|
||||
|
||||
private
|
||||
|
||||
def verify_learn_gitlab_available!
|
||||
access_denied! unless helpers.learn_gitlab_enabled?(project)
|
||||
end
|
||||
|
||||
def enable_invite_for_help_continuous_onboarding_experiment
|
||||
return unless current_user.can?(:admin_group_member, project.namespace)
|
||||
|
||||
experiment(:invite_for_help_continuous_onboarding, namespace: project.namespace) do |e|
|
||||
e.candidate {}
|
||||
end
|
||||
end
|
||||
|
||||
def enable_video_tutorials_continuous_onboarding_experiment
|
||||
experiment(:video_tutorials_continuous_onboarding, namespace: project&.namespace).publish
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -222,7 +222,13 @@ class ProjectsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def housekeeping
|
||||
::Repositories::HousekeepingService.new(@project, :gc).execute
|
||||
task = if ::Feature.enabled?(:eager_housekeeping_on_manual_jobs, @project)
|
||||
:eager
|
||||
else
|
||||
:gc
|
||||
end
|
||||
|
||||
::Repositories::HousekeepingService.new(@project, task).execute
|
||||
|
||||
redirect_to(
|
||||
project_path(@project),
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class VideoTutorialsContinuousOnboardingExperiment < ApplicationExperiment
|
||||
control {}
|
||||
candidate {}
|
||||
end
|
||||
|
|
@ -45,7 +45,7 @@ module InviteMembersHelper
|
|||
full_path: source.full_path
|
||||
}
|
||||
|
||||
if show_invite_members_for_task?(source)
|
||||
if current_user && show_invite_members_for_task?(source)
|
||||
dataset.merge!(
|
||||
tasks_to_be_done_options: tasks_to_be_done_options.to_json,
|
||||
projects: projects_for_source(source).to_json,
|
||||
|
|
@ -71,11 +71,9 @@ module InviteMembersHelper
|
|||
{}
|
||||
end
|
||||
|
||||
def show_invite_members_for_task?(source)
|
||||
return unless current_user
|
||||
|
||||
invite_for_help_continuous_onboarding = source.is_a?(Project) && experiment(:invite_for_help_continuous_onboarding, namespace: source.namespace).assigned.name == 'candidate'
|
||||
params[:open_modal] == 'invite_members_for_task' || invite_for_help_continuous_onboarding
|
||||
# Overridden in EE
|
||||
def show_invite_members_for_task?(_source)
|
||||
params[:open_modal] == 'invite_members_for_task'
|
||||
end
|
||||
|
||||
def tasks_to_be_done_options
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module LearnGitlabHelper
|
||||
IMAGE_PATH_PLAN = "learn_gitlab/section_plan.svg"
|
||||
IMAGE_PATH_DEPLOY = "learn_gitlab/section_deploy.svg"
|
||||
IMAGE_PATH_WORKSPACE = "learn_gitlab/section_workspace.svg"
|
||||
LICENSE_SCANNING_RUN_URL = 'https://docs.gitlab.com/ee/user/compliance/license_compliance/index.html'
|
||||
|
||||
def learn_gitlab_enabled?(project)
|
||||
return false unless current_user
|
||||
|
||||
learn_gitlab_onboarding_available?(project)
|
||||
end
|
||||
|
||||
def learn_gitlab_data(project)
|
||||
{
|
||||
actions: onboarding_actions_data(project).to_json,
|
||||
sections: onboarding_sections_data.to_json,
|
||||
project: onboarding_project_data(project).to_json
|
||||
}
|
||||
end
|
||||
|
||||
def learn_gitlab_onboarding_available?(project)
|
||||
Onboarding::Progress.onboarding?(project.namespace) &&
|
||||
Onboarding::LearnGitlab.new(current_user).available?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def onboarding_actions_data(project)
|
||||
attributes = onboarding_progress(project).attributes.symbolize_keys
|
||||
|
||||
action_urls(project).to_h do |action, url|
|
||||
[
|
||||
action,
|
||||
{
|
||||
url: url,
|
||||
completed: attributes[Onboarding::Progress.column_name(action)].present?,
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def onboarding_sections_data
|
||||
{
|
||||
workspace: {
|
||||
svg: image_path(IMAGE_PATH_WORKSPACE)
|
||||
},
|
||||
plan: {
|
||||
svg: image_path(IMAGE_PATH_PLAN)
|
||||
},
|
||||
deploy: {
|
||||
svg: image_path(IMAGE_PATH_DEPLOY)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def onboarding_project_data(project)
|
||||
{ name: project.name }
|
||||
end
|
||||
|
||||
def action_urls(project)
|
||||
action_issue_urls.merge(
|
||||
issue_created: project_issues_path(project),
|
||||
git_write: project_path(project),
|
||||
merge_request_created: project_merge_requests_path(project),
|
||||
user_added: project_members_url(project),
|
||||
**deploy_section_action_urls(project)
|
||||
)
|
||||
end
|
||||
|
||||
def action_issue_urls
|
||||
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,
|
||||
namespace: project.namespace,
|
||||
user: current_user,
|
||||
sticky_to: current_user
|
||||
) do |e|
|
||||
e.control { { security_scan_enabled: project_security_configuration_path(project) } }
|
||||
e.candidate do
|
||||
{
|
||||
license_scanning_run: LICENSE_SCANNING_RUN_URL,
|
||||
secure_dependency_scanning_run: project_security_configuration_path(project, anchor: 'dependency-scanning'),
|
||||
secure_dast_run: project_security_configuration_path(project, anchor: 'dast')
|
||||
}
|
||||
end
|
||||
end.run
|
||||
end
|
||||
|
||||
def learn_gitlab_project
|
||||
@learn_gitlab_project ||= Onboarding::LearnGitlab.new(current_user).project
|
||||
end
|
||||
|
||||
def onboarding_progress(project)
|
||||
Onboarding::Progress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
LearnGitlabHelper.prepend_mod_with('LearnGitlabHelper')
|
||||
|
|
@ -117,7 +117,6 @@ module SidebarsHelper
|
|||
{
|
||||
current_user: user,
|
||||
container: project,
|
||||
learn_gitlab_enabled: learn_gitlab_enabled?(project),
|
||||
current_ref: current_ref,
|
||||
ref_type: ref_type,
|
||||
jira_issues_integration: project_jira_issues_integration?,
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Onboarding
|
||||
class LearnGitlab
|
||||
PROJECT_NAME = 'Learn GitLab'
|
||||
PROJECT_NAME_ULTIMATE_TRIAL = 'Learn GitLab - Ultimate trial'
|
||||
BOARD_NAME = 'GitLab onboarding'
|
||||
LABEL_NAME = 'Novice'
|
||||
|
||||
def initialize(current_user)
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def available?
|
||||
project && board && label
|
||||
end
|
||||
|
||||
def project
|
||||
@project ||= current_user.projects.find_by_name([PROJECT_NAME, PROJECT_NAME_ULTIMATE_TRIAL])
|
||||
end
|
||||
|
||||
def board
|
||||
return unless project
|
||||
|
||||
@board ||= project.boards.find_by_name(BOARD_NAME)
|
||||
end
|
||||
|
||||
def label
|
||||
return unless project
|
||||
|
||||
@label ||= project.labels.find_by_name(LABEL_NAME)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :current_user
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
- breadcrumb_title _("Learn GitLab")
|
||||
- page_title _("Learn GitLab")
|
||||
- add_page_specific_style 'page_bundles/learn_gitlab'
|
||||
- data = learn_gitlab_data(@project)
|
||||
|
||||
= render 'projects/invite_members_modal', project: @project
|
||||
|
||||
#js-learn-gitlab-app{ data: data }
|
||||
|
|
@ -57,7 +57,7 @@ module GitGarbageCollectMethods
|
|||
end
|
||||
|
||||
def gc?(task)
|
||||
task == :gc || task == :prune
|
||||
%i[gc eager prune].include?(task)
|
||||
end
|
||||
|
||||
def try_obtain_lease(key)
|
||||
|
|
@ -84,8 +84,11 @@ module GitGarbageCollectMethods
|
|||
repository = resource.repository.raw_repository
|
||||
client = repository.gitaly_repository_client
|
||||
|
||||
if task == :prune
|
||||
case task
|
||||
when :prune
|
||||
client.prune_unreachable_objects
|
||||
when :eager
|
||||
client.optimize_repository(eager: true)
|
||||
else
|
||||
client.optimize_repository
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: eager_housekeeping_on_manual_jobs
|
||||
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108970'
|
||||
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/388097'
|
||||
milestone: '15.9'
|
||||
type: development
|
||||
group: group::gitaly
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: invite_for_help_continuous_onboarding
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73846
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345708
|
||||
milestone: '14.5'
|
||||
type: experiment
|
||||
group: group::activation
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
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::acquisition
|
||||
default_enabled: false
|
||||
|
|
@ -92,8 +92,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
get :learn_gitlab, action: :index, controller: 'learn_gitlab'
|
||||
|
||||
namespace :ci do
|
||||
resource :lint, only: [:show, :create]
|
||||
resource :pipeline_editor, only: [:show], controller: :pipeline_editor, path: 'editor'
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ works with a set of [ETL](#etl) Pipelines leveraging from the current [GitLab AP
|
|||
|
||||

|
||||
|
||||
### [ETL](https://www.ibm.com/cloud/learn/etl)
|
||||
### [ETL](https://www.ibm.com/topics/etl)
|
||||
|
||||
<!-- Direct quote from the IBM URL link -->
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ If you have any questions or need help, visit [Getting Help](https://about.gitla
|
|||
communicate with the GitLab community. GitLab prefers [asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real-time communication.
|
||||
|
||||
We do encourage you to connect and hang out with us. GitLab has a Gitter room dedicated for [contributors](https://gitter.im/gitlab/contributors), which is bridged with our
|
||||
internal Slack. We actively monitor this channel. There is also a community-run [Discord server](https://discord.gg/gitlab) where you can
|
||||
internal Slack. We actively monitor this channel. There is also a community-run [Discord server](https://discord.com/invite/gitlab) where you can
|
||||
find other contributors in the `#contributors` channel.
|
||||
|
||||
Thanks for your contribution!
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
|
|||
**General resources**:
|
||||
|
||||
- [📚 Official Introduction to GraphQL](https://graphql.org/learn/)
|
||||
- [📚 Official Introduction to Apollo](https://www.apollographql.com/tutorials/fullstack-quickstart/introduction)
|
||||
- [📚 Official Introduction to Apollo](https://www.apollographql.com/tutorials/fullstack-quickstart/01-introduction)
|
||||
|
||||
**GraphQL at GitLab**:
|
||||
|
||||
|
|
|
|||
|
|
@ -872,7 +872,13 @@ module API
|
|||
authorize_admin_project
|
||||
|
||||
begin
|
||||
::Repositories::HousekeepingService.new(user_project, :gc).execute
|
||||
task = if ::Feature.enabled?(:eager_housekeeping_on_manual_jobs, user_project)
|
||||
:eager
|
||||
else
|
||||
:gc
|
||||
end
|
||||
|
||||
::Repositories::HousekeepingService.new(user_project, task).execute
|
||||
rescue ::Repositories::HousekeepingService::LeaseTaken => error
|
||||
conflict!(error.message)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,8 +24,20 @@ module Gitlab
|
|||
response.exists
|
||||
end
|
||||
|
||||
def optimize_repository
|
||||
request = Gitaly::OptimizeRepositoryRequest.new(repository: @gitaly_repo)
|
||||
# Optimize the repository. By default, this will perform heuristical housekeeping in the repository, which
|
||||
# is the recommended approach and will only optimize what needs to be optimized. If `eager = true`, then
|
||||
# Gitaly will instead be asked to perform eager housekeeping. As a consequence the housekeeping run will take a
|
||||
# _lot_ longer. It is not recommended to use eager housekeeping in general, but only in situations where it is
|
||||
# explicitly required.
|
||||
def optimize_repository(eager: false)
|
||||
strategy = if eager
|
||||
Gitaly::OptimizeRepositoryRequest::Strategy::STRATEGY_EAGER
|
||||
else
|
||||
Gitaly::OptimizeRepositoryRequest::Strategy::STRATEGY_HEURISTICAL
|
||||
end
|
||||
|
||||
request = Gitaly::OptimizeRepositoryRequest.new(repository: @gitaly_repo,
|
||||
strategy: strategy)
|
||||
gitaly_client_call(@storage, :repository_service, :optimize_repository, request, timeout: GitalyClient.long_timeout)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Projects
|
||||
module Menus
|
||||
class LearnGitlabMenu < ::Sidebars::Menu
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
override :link
|
||||
def link
|
||||
project_learn_gitlab_path(context.project)
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: :learn_gitlab }
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Learn GitLab')
|
||||
end
|
||||
|
||||
override :has_pill?
|
||||
def has_pill?
|
||||
context.learn_gitlab_enabled
|
||||
end
|
||||
|
||||
override :pill_count
|
||||
def pill_count
|
||||
strong_memoize(:pill_count) do
|
||||
percentage = Onboarding::Completion.new(
|
||||
context.project.namespace,
|
||||
context.current_user
|
||||
).percentage
|
||||
|
||||
"#{percentage}%"
|
||||
end
|
||||
end
|
||||
|
||||
override :extra_nav_link_html_options
|
||||
def extra_nav_link_html_options
|
||||
{
|
||||
class: 'home',
|
||||
data: {
|
||||
track_label: 'learn_gitlab'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'bulb'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
context.learn_gitlab_enabled
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -19,7 +19,6 @@ module Sidebars
|
|||
|
||||
def add_menus
|
||||
add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::ExternalIssueTrackerMenu.new(context))
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::LearnGitlabController, feature_category: :onboarding do
|
||||
describe 'GET #index' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: create(:group)) }
|
||||
|
||||
let(:learn_gitlab_enabled) { true }
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
|
||||
|
||||
subject(:action) { get :index, params: params }
|
||||
|
||||
before do
|
||||
project.namespace.add_owner(user)
|
||||
allow(controller.helpers).to receive(:learn_gitlab_enabled?).and_return(learn_gitlab_enabled)
|
||||
end
|
||||
|
||||
context 'for unauthenticated user' do
|
||||
it { is_expected.to have_gitlab_http_status(:redirect) }
|
||||
end
|
||||
|
||||
context 'for authenticated user' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to render_template(:index) }
|
||||
|
||||
context 'when learn_gitlab is not available' do
|
||||
let(:learn_gitlab_enabled) { false }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with invite_for_help_continuous_onboarding experiment' do
|
||||
it 'tracks the assignment', :experiment do
|
||||
stub_experiments(invite_for_help_continuous_onboarding: true)
|
||||
|
||||
expect(experiment(:invite_for_help_continuous_onboarding))
|
||||
.to track(:assignment).with_context(namespace: project.namespace).on_next_instance
|
||||
|
||||
action
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -633,45 +633,67 @@ RSpec.describe ProjectsController do
|
|||
|
||||
let(:housekeeping) { Repositories::HousekeepingService.new(project) }
|
||||
|
||||
context 'when authenticated as owner' do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
sign_in(user)
|
||||
shared_examples 'a housekeeping job' do
|
||||
context 'when authenticated as owner' do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
sign_in(user)
|
||||
|
||||
allow(Repositories::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping)
|
||||
allow(Repositories::HousekeepingService).to receive(:new).with(project, expected_task).and_return(housekeeping)
|
||||
end
|
||||
|
||||
it 'forces a full garbage collection' do
|
||||
expect(housekeeping).to receive(:execute).once
|
||||
|
||||
post :housekeeping,
|
||||
params: {
|
||||
namespace_id: project.namespace.path,
|
||||
id: project.path
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
|
||||
it 'forces a full garbage collection' do
|
||||
expect(housekeeping).to receive(:execute).once
|
||||
context 'when authenticated as developer' do
|
||||
let(:developer) { create(:user) }
|
||||
|
||||
post :housekeeping,
|
||||
params: {
|
||||
namespace_id: project.namespace.path,
|
||||
id: project.path
|
||||
}
|
||||
before do
|
||||
group.add_developer(developer)
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
it 'does not execute housekeeping' do
|
||||
expect(housekeeping).not_to receive(:execute)
|
||||
|
||||
post :housekeeping,
|
||||
params: {
|
||||
namespace_id: project.namespace.path,
|
||||
id: project.path
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as developer' do
|
||||
let(:developer) { create(:user) }
|
||||
context 'with :eager_housekeeping_on_manual_jobs disabled' do
|
||||
let(:expected_task) { :gc }
|
||||
|
||||
before do
|
||||
group.add_developer(developer)
|
||||
stub_feature_flags(eager_housekeeping_on_manual_jobs: false)
|
||||
end
|
||||
|
||||
it 'does not execute housekeeping' do
|
||||
expect(housekeeping).not_to receive(:execute)
|
||||
it_behaves_like 'a housekeeping job'
|
||||
end
|
||||
|
||||
post :housekeeping,
|
||||
params: {
|
||||
namespace_id: project.namespace.path,
|
||||
id: project.path
|
||||
}
|
||||
context 'with :eager_housekeeping_on_manual_jobs enabled' do
|
||||
let(:expected_task) { :eager }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
before do
|
||||
stub_feature_flags(eager_housekeeping_on_manual_jobs: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'a housekeeping job'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Learn GitLab Section Card renders correctly 1`] = `
|
||||
<gl-card-stub
|
||||
bodyclass="gl-pt-0"
|
||||
class="gl-pt-0 h-100"
|
||||
footerclass=""
|
||||
headerclass="gl-bg-white gl-border-0 gl-pb-0"
|
||||
>
|
||||
<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>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="userAdded"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="issueCreated"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="gitWrite"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="mergeRequestCreated"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="securityScanEnabled"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="pipelineCreated"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="trialStarted"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="codeOwnersEnabled"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="requiredMrApprovalsEnabled"
|
||||
value="[object Object]"
|
||||
/>
|
||||
</gl-card-stub>
|
||||
`;
|
||||
|
|
@ -1,409 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Learn GitLab renders correctly 1`] = `
|
||||
<div>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="gl-mb-7 gl-ml-5"
|
||||
>
|
||||
<h1
|
||||
class="gl-font-size-h1"
|
||||
>
|
||||
Learn GitLab
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="gl-text-gray-700 gl-mb-0"
|
||||
>
|
||||
Ready to get started with GitLab? Follow these steps to set up your workspace, plan and commit changes, and deploy your project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="gl-mb-3"
|
||||
>
|
||||
<p
|
||||
class="gl-text-gray-500 gl-mb-2"
|
||||
data-testid="completion-percentage"
|
||||
>
|
||||
22% completed
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="progress"
|
||||
max="9"
|
||||
value="2"
|
||||
>
|
||||
<div
|
||||
aria-valuemax="9"
|
||||
aria-valuemin="0"
|
||||
aria-valuenow="2"
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
style="width: 22.22222222222222%;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
|
||||
>
|
||||
<div
|
||||
class="gl-card gl-pt-0 h-100"
|
||||
>
|
||||
<div
|
||||
class="gl-card-header gl-bg-white gl-border-0 gl-pb-0"
|
||||
>
|
||||
<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>
|
||||
|
||||
<div
|
||||
class="gl-card-body gl-pt-0"
|
||||
>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<span
|
||||
class="gl-text-green-500"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<span
|
||||
class="gl-text-green-500"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="gl-icon s16"
|
||||
data-testid="completed-icon"
|
||||
role="img"
|
||||
>
|
||||
<use
|
||||
href="#check-circle-filled"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Create a repository
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_link"
|
||||
data-testid="uncompleted-learn-gitlab-link"
|
||||
data-track-action="click_link"
|
||||
data-track-label="set_up_your_first_project_s_ci_cd"
|
||||
href="http://example.com/"
|
||||
target="_self"
|
||||
>
|
||||
Set up your first project's CI/CD
|
||||
</a>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_link"
|
||||
data-testid="uncompleted-learn-gitlab-link"
|
||||
data-track-action="click_link"
|
||||
data-track-label="start_a_free_trial_of_gitlab_ultimate"
|
||||
href="http://example.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Start a free trial of GitLab Ultimate
|
||||
</a>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_link"
|
||||
data-testid="uncompleted-learn-gitlab-link"
|
||||
data-track-action="click_link"
|
||||
data-track-label="add_code_owners"
|
||||
href="http://example.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Add code owners
|
||||
</a>
|
||||
|
||||
<span
|
||||
class="gl-font-style-italic gl-text-gray-500"
|
||||
data-testid="trial-only"
|
||||
>
|
||||
|
||||
- Included in trial
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_link"
|
||||
data-testid="uncompleted-learn-gitlab-link"
|
||||
data-track-action="click_link"
|
||||
data-track-label="enable_require_merge_approvals"
|
||||
href="http://example.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Enable require merge approvals
|
||||
</a>
|
||||
|
||||
<span
|
||||
class="gl-font-style-italic gl-text-gray-500"
|
||||
data-testid="trial-only"
|
||||
>
|
||||
|
||||
- Included in trial
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
|
||||
>
|
||||
<div
|
||||
class="gl-card gl-pt-0 h-100"
|
||||
>
|
||||
<div
|
||||
class="gl-card-header gl-bg-white gl-border-0 gl-pb-0"
|
||||
>
|
||||
<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>
|
||||
|
||||
<div
|
||||
class="gl-card-body gl-pt-0"
|
||||
>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_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>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_link"
|
||||
data-testid="uncompleted-learn-gitlab-link"
|
||||
data-track-action="click_link"
|
||||
data-track-label="submit_a_merge_request_mr"
|
||||
href="http://example.com/"
|
||||
target="_self"
|
||||
>
|
||||
Submit a merge request (MR)
|
||||
</a>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4"
|
||||
>
|
||||
<div
|
||||
class="gl-card gl-pt-0 h-100"
|
||||
>
|
||||
<div
|
||||
class="gl-card-header gl-bg-white gl-border-0 gl-pb-0"
|
||||
>
|
||||
<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>
|
||||
|
||||
<div
|
||||
class="gl-card-body gl-pt-0"
|
||||
>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<div
|
||||
class="flex align-items-center"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="gl-link"
|
||||
data-qa-selector="uncompleted_learn_gitlab_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>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import LearnGitlabSectionCard from '~/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue';
|
||||
import { testActions } from './mock_data';
|
||||
|
||||
const defaultSection = 'workspace';
|
||||
const testImage = 'workspace.svg';
|
||||
|
||||
describe('Learn GitLab Section Card', () => {
|
||||
let wrapper;
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const createWrapper = () => {
|
||||
wrapper = shallowMount(LearnGitlabSectionCard, {
|
||||
propsData: { section: defaultSection, actions: testActions, svg: testImage },
|
||||
});
|
||||
};
|
||||
|
||||
it('renders correctly', () => {
|
||||
createWrapper({ completed: false });
|
||||
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
import { GlPopover, GlLink } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import { stubExperiments } from 'helpers/experimentation_helper';
|
||||
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import LearnGitlabSectionLink from '~/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue';
|
||||
import { ACTION_LABELS } from '~/pages/projects/learn_gitlab/constants';
|
||||
|
||||
const defaultAction = 'gitWrite';
|
||||
const defaultProps = {
|
||||
title: 'Create Repository',
|
||||
description: 'Some description',
|
||||
url: 'https://example.com',
|
||||
completed: false,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
const openInNewTabProps = {
|
||||
url: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/',
|
||||
openInNewTab: true,
|
||||
};
|
||||
|
||||
describe('Learn GitLab Section Link', () => {
|
||||
let wrapper;
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const createWrapper = (action = defaultAction, props = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(LearnGitlabSectionLink, {
|
||||
propsData: { action, value: { ...defaultProps, ...props } },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const openInviteMembesrModalLink = () =>
|
||||
wrapper.find('[data-testid="invite-for-help-continuous-onboarding-experiment-link"]');
|
||||
|
||||
const findUncompletedLink = () => wrapper.find('[data-testid="uncompleted-learn-gitlab-link"]');
|
||||
const findDisabledLink = () => wrapper.findByTestId('disabled-learn-gitlab-link');
|
||||
const findPopoverTrigger = () => wrapper.findByTestId('contact-admin-popover-trigger');
|
||||
const findPopover = () => wrapper.findComponent(GlPopover);
|
||||
const findPopoverLink = () => findPopover().findComponent(GlLink);
|
||||
const videoTutorialLink = () => wrapper.find('[data-testid="video-tutorial-link"]');
|
||||
|
||||
it('renders no icon when not completed', () => {
|
||||
createWrapper(undefined, { completed: false });
|
||||
|
||||
expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the completion icon when completed', () => {
|
||||
createWrapper(undefined, { completed: true });
|
||||
|
||||
expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders no trial only when it is not required', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders trial only when trial is required', () => {
|
||||
createWrapper('codeOwnersEnabled');
|
||||
|
||||
expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('disabled links', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper('trialStarted', { enabled: false });
|
||||
});
|
||||
|
||||
it('renders text without a link', () => {
|
||||
expect(findDisabledLink().exists()).toBe(true);
|
||||
expect(findDisabledLink().text()).toBe(ACTION_LABELS.trialStarted.title);
|
||||
expect(findDisabledLink().attributes('href')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('renders a popover trigger with question icon', () => {
|
||||
expect(findPopoverTrigger().exists()).toBe(true);
|
||||
expect(findPopoverTrigger().props('icon')).toBe('question-o');
|
||||
expect(findPopoverTrigger().attributes('aria-label')).toBe(
|
||||
LearnGitlabSectionLink.i18n.contactAdmin,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a popover', () => {
|
||||
expect(findPopoverTrigger().attributes('id')).toBe(findPopover().props('target'));
|
||||
expect(findPopover().props()).toMatchObject({
|
||||
placement: 'top',
|
||||
triggers: 'hover focus',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders default disabled message', () => {
|
||||
expect(findPopover().text()).toContain(LearnGitlabSectionLink.i18n.contactAdmin);
|
||||
});
|
||||
|
||||
it('renders custom disabled message if provided', () => {
|
||||
createWrapper('trialStarted', { enabled: false, message: 'Custom message' });
|
||||
expect(findPopover().text()).toContain('Custom message');
|
||||
});
|
||||
|
||||
it('renders a link inside the popover', () => {
|
||||
expect(findPopoverLink().exists()).toBe(true);
|
||||
expect(findPopoverLink().attributes('href')).toBe(defaultProps.url);
|
||||
});
|
||||
});
|
||||
|
||||
describe('links marked with openInNewTab', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper('securityScanEnabled', openInNewTabProps);
|
||||
});
|
||||
|
||||
it('renders links with blank target', () => {
|
||||
const linkElement = findUncompletedLink();
|
||||
|
||||
expect(linkElement.exists()).toBe(true);
|
||||
expect(linkElement.attributes('target')).toEqual('_blank');
|
||||
});
|
||||
|
||||
it('tracks the click', () => {
|
||||
const trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
|
||||
findUncompletedLink().trigger('click');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
|
||||
label: 'run_a_security_scan_using_ci_cd',
|
||||
});
|
||||
|
||||
unmockTracking();
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering a link to open the invite_members modal instead of a regular link', () => {
|
||||
it.each`
|
||||
action | experimentVariant | showModal
|
||||
${'userAdded'} | ${'candidate'} | ${true}
|
||||
${'userAdded'} | ${'control'} | ${false}
|
||||
${defaultAction} | ${'candidate'} | ${false}
|
||||
${defaultAction} | ${'control'} | ${false}
|
||||
`(
|
||||
'when the invite_for_help_continuous_onboarding experiment has variant: $experimentVariant and action is $action, the modal link is shown: $showModal',
|
||||
({ action, experimentVariant, showModal }) => {
|
||||
stubExperiments({ invite_for_help_continuous_onboarding: experimentVariant });
|
||||
createWrapper(action);
|
||||
|
||||
expect(openInviteMembesrModalLink().exists()).toBe(showModal);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('clicking the link to open the invite_members modal', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation();
|
||||
|
||||
stubExperiments({ invite_for_help_continuous_onboarding: 'candidate' });
|
||||
createWrapper('userAdded');
|
||||
});
|
||||
|
||||
it('calls the eventHub', () => {
|
||||
openInviteMembesrModalLink().vm.$emit('click');
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('openModal', { source: 'learn_gitlab' });
|
||||
});
|
||||
|
||||
it('tracks the click', async () => {
|
||||
const trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
|
||||
triggerEvent(openInviteMembesrModalLink().element);
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
|
||||
label: 'invite_your_colleagues',
|
||||
property: 'Growth::Activation::Experiment::InviteForHelpContinuousOnboarding',
|
||||
});
|
||||
|
||||
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',
|
||||
context: {
|
||||
data: {
|
||||
experiment: 'video_tutorials_continuous_onboarding',
|
||||
variant: 'candidate',
|
||||
},
|
||||
schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0',
|
||||
},
|
||||
});
|
||||
|
||||
unmockTracking();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import { GlProgressBar, GlAlert } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import Cookies from '~/lib/utils/cookies';
|
||||
import LearnGitlab from '~/pages/projects/learn_gitlab/components/learn_gitlab.vue';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import { INVITE_MODAL_OPEN_COOKIE } from '~/pages/projects/learn_gitlab/constants';
|
||||
import { testActions, testSections, testProject } from './mock_data';
|
||||
|
||||
describe('Learn GitLab', () => {
|
||||
let wrapper;
|
||||
let sidebar;
|
||||
|
||||
const createWrapper = () => {
|
||||
wrapper = mount(LearnGitlab, {
|
||||
propsData: {
|
||||
actions: testActions,
|
||||
sections: testSections,
|
||||
project: testProject,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
sidebar = document.createElement('div');
|
||||
sidebar.innerHTML = `
|
||||
<div class="sidebar-top-level-items">
|
||||
<div class="active">
|
||||
<div class="count"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(sidebar);
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
sidebar.remove();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the progress percentage', () => {
|
||||
const text = wrapper.find('[data-testid="completion-percentage"]').text();
|
||||
|
||||
expect(text).toBe('22% completed');
|
||||
});
|
||||
|
||||
it('renders the progress bar with correct values', () => {
|
||||
const progressBar = wrapper.findComponent(GlProgressBar);
|
||||
|
||||
expect(progressBar.attributes('value')).toBe('2');
|
||||
expect(progressBar.attributes('max')).toBe('9');
|
||||
});
|
||||
|
||||
describe('Invite Members Modal', () => {
|
||||
let spy;
|
||||
let cookieSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = jest.spyOn(eventHub, '$emit');
|
||||
cookieSpy = jest.spyOn(Cookies, 'remove');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Cookies.remove(INVITE_MODAL_OPEN_COOKIE);
|
||||
});
|
||||
|
||||
it('emits openModal', () => {
|
||||
Cookies.set(INVITE_MODAL_OPEN_COOKIE, true);
|
||||
|
||||
createWrapper();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('openModal', {
|
||||
mode: 'celebrate',
|
||||
source: 'learn-gitlab',
|
||||
});
|
||||
expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
|
||||
});
|
||||
|
||||
it('does not emit openModal when cookie is not set', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the showSuccessfulInvitationsAlert event is fired', () => {
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
beforeEach(() => {
|
||||
eventHub.$emit('showSuccessfulInvitationsAlert');
|
||||
});
|
||||
|
||||
it('displays the successful invitations alert', () => {
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays a message with the project name', () => {
|
||||
expect(findAlert().text()).toBe(
|
||||
"Your team is growing! You've successfully invited new team members to the test-project project.",
|
||||
);
|
||||
});
|
||||
|
||||
it('modifies the sidebar percentage', () => {
|
||||
expect(sidebar.textContent.trim()).toBe('22%');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import IncludedInTrialIndicator from '~/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue';
|
||||
|
||||
describe('Learn GitLab Trial Card', () => {
|
||||
it('renders correctly', () => {
|
||||
const wrapper = shallowMount(IncludedInTrialIndicator);
|
||||
|
||||
expect(wrapper.text()).toEqual('- Included in trial');
|
||||
|
||||
wrapper.destroy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
export const testActions = {
|
||||
gitWrite: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
userAdded: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
pipelineCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
trialStarted: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
codeOwnersEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
requiredMrApprovalsEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
mergeRequestCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
securityScanEnabled: {
|
||||
url: 'https://docs.gitlab.com/ee/foobar/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
openInNewTab: true,
|
||||
},
|
||||
issueCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const testSections = {
|
||||
workspace: {
|
||||
svg: 'workspace.svg',
|
||||
},
|
||||
deploy: {
|
||||
svg: 'deploy.svg',
|
||||
},
|
||||
plan: {
|
||||
svg: 'plan.svg',
|
||||
},
|
||||
};
|
||||
|
||||
export const testProject = {
|
||||
name: 'test-project',
|
||||
};
|
||||
|
|
@ -36,7 +36,8 @@ RSpec.describe InviteMembersHelper do
|
|||
end
|
||||
|
||||
it 'provides the correct attributes' do
|
||||
expect(helper.common_invite_group_modal_data(group, GroupMember, 'false')).to include({ groups_filter: 'descendant_groups', parent_id: group.id })
|
||||
expect(helper.common_invite_group_modal_data(group, GroupMember, 'false'))
|
||||
.to include({ groups_filter: 'descendant_groups', parent_id: group.id })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -46,7 +47,8 @@ RSpec.describe InviteMembersHelper do
|
|||
end
|
||||
|
||||
it 'does not return filter attributes' do
|
||||
expect(helper.common_invite_group_modal_data(project.group, ProjectMember, 'true').keys).not_to include(:groups_filter, :parent_id)
|
||||
expect(helper.common_invite_group_modal_data(project.group, ProjectMember, 'true').keys)
|
||||
.not_to include(:groups_filter, :parent_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -64,7 +66,7 @@ RSpec.describe InviteMembersHelper do
|
|||
expect(helper.common_invite_modal_dataset(project)).to include(attributes)
|
||||
end
|
||||
|
||||
context 'tasks_to_be_done' do
|
||||
context 'with tasks_to_be_done' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
subject(:output) { helper.common_invite_modal_dataset(source) }
|
||||
|
|
@ -79,9 +81,7 @@ RSpec.describe InviteMembersHelper do
|
|||
{ value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
|
||||
].to_json
|
||||
)
|
||||
expect(output[:projects]).to eq(
|
||||
[{ id: project.id, title: project.title }].to_json
|
||||
)
|
||||
expect(output[:projects]).to eq([{ id: project.id, title: project.title }].to_json)
|
||||
expect(output[:new_project_path]).to eq(
|
||||
source.is_a?(Project) ? '' : new_project_path(namespace_id: group.id)
|
||||
)
|
||||
|
|
@ -93,8 +93,8 @@ RSpec.describe InviteMembersHelper do
|
|||
end
|
||||
end
|
||||
|
||||
context 'inviting members for tasks' do
|
||||
where(:open_modal_param_present?, :logged_in?, :expected?) do
|
||||
context 'when inviting members for tasks' do
|
||||
where(:open_modal_param?, :logged_in?, :expected?) do
|
||||
true | true | true
|
||||
true | false | false
|
||||
false | true | false
|
||||
|
|
@ -104,7 +104,7 @@ RSpec.describe InviteMembersHelper do
|
|||
with_them do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(developer) if logged_in?
|
||||
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' }) if open_modal_param_present?
|
||||
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' }) if open_modal_param?
|
||||
end
|
||||
|
||||
context 'when the source is a project' do
|
||||
|
|
@ -120,36 +120,6 @@ RSpec.describe InviteMembersHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'the invite_for_help_continuous_onboarding experiment' do
|
||||
where(:invite_for_help_continuous_onboarding?, :logged_in?, :expected?) do
|
||||
true | true | true
|
||||
true | false | false
|
||||
false | true | false
|
||||
false | false | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(developer) if logged_in?
|
||||
stub_experiments(invite_for_help_continuous_onboarding: :candidate) if invite_for_help_continuous_onboarding?
|
||||
end
|
||||
|
||||
context 'when the source is a project' do
|
||||
let_it_be(:source) { project }
|
||||
|
||||
it_behaves_like 'including the tasks to be done attributes'
|
||||
end
|
||||
|
||||
context 'when the source is a group' do
|
||||
let_it_be(:source) { group }
|
||||
|
||||
let(:expected?) { false }
|
||||
|
||||
it_behaves_like 'including the tasks to be done attributes'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -172,11 +142,9 @@ RSpec.describe InviteMembersHelper do
|
|||
end
|
||||
|
||||
context 'when the user can not manage project members' do
|
||||
before do
|
||||
expect(helper).to receive(:can?).with(owner, :admin_project_member, project).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(helper).to receive(:can?).with(owner, :admin_project_member, project).and_return(false)
|
||||
|
||||
expect(helper.can_invite_members_for_project?(project)).to eq false
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe LearnGitlabHelper, feature_category: :onboarding do
|
||||
include AfterNextHelpers
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
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(Onboarding::LearnGitlab) do |learn_gitlab|
|
||||
allow(learn_gitlab).to receive(:project).and_return(project)
|
||||
end
|
||||
|
||||
Onboarding::Progress.onboard(namespace)
|
||||
Onboarding::Progress.register(namespace, :git_write)
|
||||
end
|
||||
|
||||
describe '#learn_gitlab_enabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
|
||||
|
||||
subject { helper.learn_gitlab_enabled?(project) }
|
||||
|
||||
where(:onboarding, :learn_gitlab_available, :result) do
|
||||
true | true | true
|
||||
true | false | false
|
||||
false | true | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow(Onboarding::Progress).to receive(:onboarding?).with(project.namespace).and_return(onboarding)
|
||||
allow_next(Onboarding::LearnGitlab, user).to receive(:available?).and_return(learn_gitlab_available)
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#learn_gitlab_data' do
|
||||
subject(:learn_gitlab_data) { helper.learn_gitlab_data(project) }
|
||||
|
||||
let(:onboarding_actions_data) { Gitlab::Json.parse(learn_gitlab_data[:actions]).deep_symbolize_keys }
|
||||
let(:onboarding_sections_data) { Gitlab::Json.parse(learn_gitlab_data[:sections]).deep_symbolize_keys }
|
||||
let(:onboarding_project_data) { Gitlab::Json.parse(learn_gitlab_data[:project]).deep_symbolize_keys }
|
||||
|
||||
shared_examples 'has all data' do
|
||||
it 'has all actions' do
|
||||
expected_keys = [
|
||||
:issue_created,
|
||||
:git_write,
|
||||
:pipeline_created,
|
||||
:merge_request_created,
|
||||
:user_added,
|
||||
:trial_started,
|
||||
:required_mr_approvals_enabled,
|
||||
:code_owners_enabled,
|
||||
:security_scan_enabled
|
||||
]
|
||||
|
||||
expect(onboarding_actions_data.keys).to contain_exactly(*expected_keys)
|
||||
end
|
||||
|
||||
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(&:keys)).to match_array([[:svg]] * 3)
|
||||
end
|
||||
|
||||
it 'has all project data', :aggregate_failures do
|
||||
expect(onboarding_project_data.keys).to contain_exactly(:name)
|
||||
expect(onboarding_project_data.values).to match_array([project.name])
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'has all data'
|
||||
|
||||
it 'sets correct completion statuses' do
|
||||
expect(onboarding_actions_data).to match({
|
||||
issue_created: a_hash_including(completed: false),
|
||||
git_write: a_hash_including(completed: true),
|
||||
pipeline_created: a_hash_including(completed: false),
|
||||
merge_request_created: a_hash_including(completed: false),
|
||||
user_added: a_hash_including(completed: false),
|
||||
trial_started: a_hash_including(completed: false),
|
||||
required_mr_approvals_enabled: a_hash_including(completed: false),
|
||||
code_owners_enabled: a_hash_including(completed: false),
|
||||
security_scan_enabled: a_hash_including(completed: false)
|
||||
})
|
||||
end
|
||||
|
||||
describe 'security_actions_continuous_onboarding experiment' do
|
||||
let(:base_paths) do
|
||||
{
|
||||
trial_started: a_hash_including(url: %r{/learn_gitlab/-/issues/2\z}),
|
||||
pipeline_created: a_hash_including(url: %r{/learn_gitlab/-/issues/7\z}),
|
||||
code_owners_enabled: a_hash_including(url: %r{/learn_gitlab/-/issues/10\z}),
|
||||
required_mr_approvals_enabled: a_hash_including(url: %r{/learn_gitlab/-/issues/11\z}),
|
||||
issue_created: a_hash_including(url: %r{/learn_gitlab/-/issues\z}),
|
||||
git_write: a_hash_including(url: %r{/learn_gitlab\z}),
|
||||
user_added: a_hash_including(url: %r{/learn_gitlab/-/project_members\z}),
|
||||
merge_request_created: a_hash_including(url: %r{/learn_gitlab/-/merge_requests\z})
|
||||
}
|
||||
end
|
||||
|
||||
context 'when control' do
|
||||
before do
|
||||
stub_experiments(security_actions_continuous_onboarding: :control)
|
||||
end
|
||||
|
||||
it 'sets correct paths' do
|
||||
expect(onboarding_actions_data).to match(
|
||||
base_paths.merge(
|
||||
security_scan_enabled: a_hash_including(
|
||||
url: %r{/learn_gitlab/-/security/configuration\z}
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when candidate' do
|
||||
before do
|
||||
stub_experiments(security_actions_continuous_onboarding: :candidate)
|
||||
end
|
||||
|
||||
it 'sets correct paths' do
|
||||
expect(onboarding_actions_data).to match(
|
||||
base_paths.merge(
|
||||
license_scanning_run: a_hash_including(
|
||||
url: described_class::LICENSE_SCANNING_RUN_URL
|
||||
),
|
||||
secure_dependency_scanning_run: a_hash_including(
|
||||
url: project_security_configuration_path(project, anchor: 'dependency-scanning')
|
||||
),
|
||||
secure_dast_run: a_hash_including(
|
||||
url: project_security_configuration_path(project, anchor: 'dast')
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::GitalyClient::RepositoryService do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:project) { create(:project) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let(:storage_name) { project.repository_storage }
|
||||
let(:relative_path) { project.disk_path + '.git' }
|
||||
let(:client) { described_class.new(project.repository) }
|
||||
|
|
@ -22,13 +22,38 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
|
|||
end
|
||||
|
||||
describe '#optimize_repository' do
|
||||
it 'sends a optimize_repository message' do
|
||||
expect_any_instance_of(Gitaly::RepositoryService::Stub)
|
||||
.to receive(:optimize_repository)
|
||||
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
|
||||
.and_return(double(:optimize_repository))
|
||||
shared_examples 'a repository optimization' do
|
||||
it 'sends a optimize_repository message' do
|
||||
expect_any_instance_of(Gitaly::RepositoryService::Stub)
|
||||
.to receive(:optimize_repository)
|
||||
.with(gitaly_request_with_params(
|
||||
strategy: expected_strategy
|
||||
), kind_of(Hash))
|
||||
.and_call_original
|
||||
|
||||
client.optimize_repository
|
||||
client.optimize_repository(**params)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default parameter' do
|
||||
let(:params) { {} }
|
||||
let(:expected_strategy) { :STRATEGY_HEURISTICAL }
|
||||
|
||||
it_behaves_like 'a repository optimization'
|
||||
end
|
||||
|
||||
context 'with heuristical housekeeping strategy' do
|
||||
let(:params) { { eager: false } }
|
||||
let(:expected_strategy) { :STRATEGY_HEURISTICAL }
|
||||
|
||||
it_behaves_like 'a repository optimization'
|
||||
end
|
||||
|
||||
context 'with eager housekeeping strategy' do
|
||||
let(:params) { { eager: true } }
|
||||
let(:expected_strategy) { :STRATEGY_EAGER }
|
||||
|
||||
it_behaves_like 'a repository optimization'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Projects::Menus::LearnGitlabMenu do
|
||||
let_it_be(:project) { build(:project) }
|
||||
let_it_be(:learn_gitlab_enabled) { true }
|
||||
|
||||
let(:context) do
|
||||
Sidebars::Projects::Context.new(
|
||||
current_user: nil,
|
||||
container: project,
|
||||
learn_gitlab_enabled: learn_gitlab_enabled
|
||||
)
|
||||
end
|
||||
|
||||
subject { described_class.new(context) }
|
||||
|
||||
it 'does not contain any sub menu' do
|
||||
expect(subject.has_items?).to be false
|
||||
end
|
||||
|
||||
describe '#nav_link_html_options' do
|
||||
let_it_be(:data_tracking) do
|
||||
{
|
||||
class: 'home',
|
||||
data: {
|
||||
track_label: 'learn_gitlab'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
specify do
|
||||
expect(subject.nav_link_html_options).to eq(data_tracking)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render?' do
|
||||
context 'when learn gitlab experiment is enabled' do
|
||||
it 'returns true' do
|
||||
expect(subject.render?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when learn gitlab experiment is disabled' do
|
||||
let(:learn_gitlab_enabled) { false }
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.render?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_pill?' do
|
||||
context 'when learn gitlab experiment is enabled' do
|
||||
it 'returns true' do
|
||||
expect(subject.has_pill?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when learn gitlab experiment is disabled' do
|
||||
let(:learn_gitlab_enabled) { false }
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.has_pill?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pill_count' do
|
||||
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
|
||||
end
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Onboarding::LearnGitlab do
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
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
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project, :board, :label, :expected_result) do
|
||||
nil | nil | nil | nil
|
||||
nil | nil | true | nil
|
||||
nil | true | nil | nil
|
||||
nil | true | true | nil
|
||||
true | nil | nil | nil
|
||||
true | nil | true | nil
|
||||
true | true | nil | nil
|
||||
true | true | true | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow_next_instance_of(described_class) do |learn_gitlab|
|
||||
allow(learn_gitlab).to receive(:project).and_return(project)
|
||||
allow(learn_gitlab).to receive(:board).and_return(board)
|
||||
allow(learn_gitlab).to receive(:label).and_return(label)
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new(current_user).available? }
|
||||
|
||||
it { is_expected.to be expected_result }
|
||||
end
|
||||
end
|
||||
|
||||
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) 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
|
||||
subject { described_class.new(current_user).board }
|
||||
|
||||
it { is_expected.to eq learn_gitlab_board }
|
||||
end
|
||||
|
||||
describe '#label' do
|
||||
subject { described_class.new(current_user).label }
|
||||
|
||||
it { is_expected.to eq learn_gitlab_label }
|
||||
end
|
||||
end
|
||||
|
|
@ -4716,51 +4716,73 @@ RSpec.describe API::Projects, feature_category: :projects do
|
|||
end
|
||||
|
||||
describe 'POST /projects/:id/housekeeping' do
|
||||
let(:housekeeping) { Repositories::HousekeepingService.new(project) }
|
||||
shared_examples 'a housekeeping job' do
|
||||
let(:housekeeping) { Repositories::HousekeepingService.new(project) }
|
||||
|
||||
before do
|
||||
allow(Repositories::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping)
|
||||
end
|
||||
|
||||
context 'when authenticated as owner' do
|
||||
it 'starts the housekeeping process' do
|
||||
expect(housekeeping).to receive(:execute).once
|
||||
|
||||
post api("/projects/#{project.id}/housekeeping", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
before do
|
||||
allow(Repositories::HousekeepingService).to receive(:new).with(project, expected_task).and_return(housekeeping)
|
||||
end
|
||||
|
||||
context 'when housekeeping lease is taken' do
|
||||
it 'returns conflict' do
|
||||
expect(housekeeping).to receive(:execute).once.and_raise(Repositories::HousekeepingService::LeaseTaken)
|
||||
context 'when authenticated as owner' do
|
||||
it 'starts the housekeeping process' do
|
||||
expect(housekeeping).to receive(:execute).once
|
||||
|
||||
post api("/projects/#{project.id}/housekeeping", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to match(/Somebody already triggered housekeeping for this resource/)
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
end
|
||||
|
||||
context 'when housekeeping lease is taken' do
|
||||
it 'returns conflict' do
|
||||
expect(housekeeping).to receive(:execute).once.and_raise(Repositories::HousekeepingService::LeaseTaken)
|
||||
|
||||
post api("/projects/#{project.id}/housekeeping", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to match(/Somebody already triggered housekeeping for this resource/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as developer' do
|
||||
before do
|
||||
project_member
|
||||
end
|
||||
|
||||
it 'returns forbidden error' do
|
||||
post api("/projects/#{project.id}/housekeeping", user3)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns authentication error' do
|
||||
post api("/projects/#{project.id}/housekeeping")
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as developer' do
|
||||
context 'with :eager_housekeeping_on_manual_jobs disabled' do
|
||||
let(:expected_task) { :gc }
|
||||
|
||||
before do
|
||||
project_member
|
||||
stub_feature_flags(eager_housekeeping_on_manual_jobs: false)
|
||||
end
|
||||
|
||||
it 'returns forbidden error' do
|
||||
post api("/projects/#{project.id}/housekeeping", user3)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
it_behaves_like 'a housekeeping job'
|
||||
end
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns authentication error' do
|
||||
post api("/projects/#{project.id}/housekeeping")
|
||||
context 'with :eager_housekeeping_on_manual_jobs enabled' do
|
||||
let(:expected_task) { :eager }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
before do
|
||||
stub_feature_flags(eager_housekeeping_on_manual_jobs: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'a housekeeping job'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -966,11 +966,9 @@
|
|||
- './ee/spec/helpers/ee/groups/settings_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/hooks_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/integrations_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/invite_members_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/issuables_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/issues_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/labels_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/learn_gitlab_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/lock_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/namespaces_helper_spec.rb'
|
||||
- './ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
|
||||
|
|
@ -3633,7 +3631,6 @@
|
|||
- './spec/experiments/ios_specific_templates_experiment_spec.rb'
|
||||
- './spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb'
|
||||
- './spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb'
|
||||
- './spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb'
|
||||
- './spec/features/abuse_report_spec.rb'
|
||||
- './spec/features/action_cable_logging_spec.rb'
|
||||
- './spec/features/admin/admin_abuse_reports_spec.rb'
|
||||
|
|
@ -5160,7 +5157,6 @@
|
|||
- './spec/helpers/import_helper_spec.rb'
|
||||
- './spec/helpers/instance_configuration_helper_spec.rb'
|
||||
- './spec/helpers/integrations_helper_spec.rb'
|
||||
- './spec/helpers/invite_members_helper_spec.rb'
|
||||
- './spec/helpers/issuables_description_templates_helper_spec.rb'
|
||||
- './spec/helpers/issuables_helper_spec.rb'
|
||||
- './spec/helpers/issues_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -147,5 +147,19 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
|
|||
subject.perform(resource.id, 'prune', lease_key, lease_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'eager' do
|
||||
before do
|
||||
expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
|
||||
end
|
||||
|
||||
specify do
|
||||
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService, repository.raw_repository) do |instance|
|
||||
expect(instance).to receive(:optimize_repository).with(eager: true).and_call_original
|
||||
end
|
||||
|
||||
subject.perform(resource.id, 'eager', lease_key, lease_uuid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'layouts/nav/sidebar/_project' do
|
||||
RSpec.describe 'layouts/nav/sidebar/_project', feature_category: :navigation do
|
||||
let_it_be_with_reload(:project) { create(:project, :repository) }
|
||||
|
||||
let(:user) { project.first_owner }
|
||||
|
|
@ -67,19 +67,6 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
|
|||
end
|
||||
end
|
||||
|
||||
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(Onboarding::Completion) do |onboarding|
|
||||
expect(onboarding).to receive(:percentage).and_return(20)
|
||||
end
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Learn GitLab', href: project_learn_gitlab_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Repository' do
|
||||
it 'has a link to the project tree path' do
|
||||
render
|
||||
|
|
|
|||
Loading…
Reference in New Issue