diff options
98 files changed, 1083 insertions, 323 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 5decc83da2b..6f33057a057 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -111,8 +111,8 @@ review-stop: .review-qa-base: extends: - - .default-retry - .use-docker-in-docker + retry: 1 # This is confusing but this means "2 runs at max". image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7 stage: qa needs: ["review-deploy"] diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index e5cb55488d7..b301a54e6ac 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -14,7 +14,6 @@ Graphql/Descriptions: Exclude: - 'app/graphql/types/snippets/blob_action_enum.rb' - - 'app/graphql/types/snippets/visibility_scopes_enum.rb' - 'ee/app/graphql/ee/types/list_limit_metric_enum.rb' - 'ee/app/graphql/types/epic_state_enum.rb' - 'ee/app/graphql/types/health_status_enum.rb' diff --git a/Gemfile.lock b/Gemfile.lock index f6d6e07d3a3..fcf5acf6c29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -622,7 +622,7 @@ GEM http-cookie (1.0.3) domain_name (~> 0.5) http-form_data (2.3.0) - http-parser (1.2.1) + http-parser (1.2.3) ffi-compiler (>= 1.0, < 2.0) httparty (0.16.4) mime-types (~> 3.0) diff --git a/app/assets/images/mailers/in_product_marketing/experience-0.png b/app/assets/images/mailers/in_product_marketing/experience-0.png Binary files differnew file mode 100644 index 00000000000..346204d1db1 --- /dev/null +++ b/app/assets/images/mailers/in_product_marketing/experience-0.png diff --git a/app/assets/javascripts/emoji/components/picker.vue b/app/assets/javascripts/emoji/components/picker.vue index e08d294b8c5..35f0a1a3818 100644 --- a/app/assets/javascripts/emoji/components/picker.vue +++ b/app/assets/javascripts/emoji/components/picker.vue @@ -23,6 +23,11 @@ export default { required: false, default: () => [], }, + dropdownClass: { + type: [Array, String, Object], + required: false, + default: () => [], + }, }, data() { return { @@ -78,6 +83,7 @@ export default { ref="dropdown" :toggle-class="toggleClass" :boundary="getBoundaryElement()" + :class="dropdownClass" menu-class="dropdown-extended-height" category="tertiary" no-flip diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue index 93d8bcc4c19..11e9b25f9a3 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue @@ -37,7 +37,7 @@ const issueTransitionOptions = [ help: s__( 'JiraService|Automatically transitions Jira issues to the "Done" category. %{linkStart}Learn more%{linkEnd}', ), - link: helpPagePath('user/project/integrations/jira.html', { + link: helpPagePath('integration/jira/index.html', { anchor: 'automatic-issue-transitions', }), }, @@ -47,7 +47,7 @@ const issueTransitionOptions = [ help: s__( 'JiraService|Set a custom final state by using transition IDs. %{linkStart}Learn about transition IDs%{linkEnd}', ), - link: helpPagePath('user/project/integrations/jira.html', { + link: helpPagePath('integration/jira/index.html', { anchor: 'custom-issue-transitions', }), }, diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index c754af5c7de..758a799b37f 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -18,6 +18,7 @@ import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants'; import { __, s__, sprintf } from '~/locale'; import { updateUserStatus } from '~/rest_api'; import { timeRanges } from '~/vue_shared/constants'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import EmojiMenuInModal from './emoji_menu_in_modal'; import { isUserBusy } from './utils'; @@ -44,10 +45,12 @@ export default { GlFormCheckbox, GlDropdown, GlDropdownItem, + EmojiPicker: () => import('~/emoji/components/picker.vue'), }, directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], props: { defaultEmoji: { type: String, @@ -102,7 +105,9 @@ export default { this.$root.$emit(BV_SHOW_MODAL, this.modalId); }, beforeDestroy() { - this.emojiMenu.destroy(); + if (this.emojiMenu) { + this.emojiMenu.destroy(); + } }, methods: { closeModal() { @@ -121,13 +126,16 @@ export default { this.noEmoji = this.emoji === ''; this.defaultEmojiTag = Emoji.glEmojiTag(this.defaultEmoji); - this.emojiMenu = new EmojiMenuInModal( - Emoji, - toggleEmojiMenuButtonSelector, - emojiMenuClass, - this.setEmoji, - this.$refs.userStatusForm, - ); + if (!this.glFeatures.improvedEmojiPicker) { + this.emojiMenu = new EmojiMenuInModal( + Emoji, + toggleEmojiMenuButtonSelector, + emojiMenuClass, + this.setEmoji, + this.$refs.userStatusForm, + ); + } + this.setDefaultEmoji(); }) .catch(() => createFlash(__('Failed to load emoji list.'))); @@ -164,7 +172,12 @@ export default { this.emoji = emoji; this.noEmoji = false; this.clearEmoji(); - this.emojiTag = emojiTag; + + if (this.glFeatures.improvedEmojiPicker) { + this.emojiTag = Emoji.glEmojiTag(this.emoji); + } else { + this.emojiTag = emojiTag; + } }, clearEmoji() { if (this.emojiTag) { @@ -241,7 +254,26 @@ export default { <div ref="userStatusForm" class="form-group position-relative m-0"> <div class="input-group gl-mb-5"> <span class="input-group-prepend"> + <emoji-picker + v-if="glFeatures.improvedEmojiPicker" + dropdown-class="gl-h-full" + toggle-class="btn emoji-menu-toggle-button gl-px-4! gl-rounded-top-right-none! gl-rounded-bottom-right-none!" + @click="setEmoji" + > + <template #button-content> + <span v-html="emojiTag"></span> + <span + v-show="noEmoji" + class="js-no-emoji-placeholder no-emoji-placeholder position-relative" + > + <gl-icon name="slight-smile" class="award-control-icon-neutral" /> + <gl-icon name="smiley" class="award-control-icon-positive" /> + <gl-icon name="smile" class="award-control-icon-super-positive" /> + </span> + </template> + </emoji-picker> <button + v-else ref="toggleEmojiMenuButton" v-gl-tooltip.bottom.hover :title="s__('SetStatusModal|Add status emoji')" diff --git a/app/controllers/admin/background_migrations_controller.rb b/app/controllers/admin/background_migrations_controller.rb new file mode 100644 index 00000000000..c1dffbf423d --- /dev/null +++ b/app/controllers/admin/background_migrations_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class Admin::BackgroundMigrationsController < Admin::ApplicationController + feature_category :database + + def index + @relations_by_tab = { + 'queued' => batched_migration_class.queued.queue_order, + 'failed' => batched_migration_class.failed.queue_order, + 'finished' => batched_migration_class.finished.queue_order.reverse_order + } + + @current_tab = @relations_by_tab.key?(params[:tab]) ? params[:tab] : 'queued' + @migrations = @relations_by_tab[@current_tab].page(params[:page]) + @successful_rows_counts = batched_migration_class.successful_rows_counts(@migrations.map(&:id)) + end + + private + + def batched_migration_class + Gitlab::Database::BackgroundMigration::BatchedMigration + end +end diff --git a/app/controllers/groups/email_campaigns_controller.rb b/app/controllers/groups/email_campaigns_controller.rb index c1e3ce519cc..8ebb15559f0 100644 --- a/app/controllers/groups/email_campaigns_controller.rb +++ b/app/controllers/groups/email_campaigns_controller.rb @@ -16,7 +16,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController def track_click if Gitlab.com? - message = Gitlab::Email::Message::InProductMarketing.for(@track).new(group: group, series: @series) + message = Gitlab::Email::Message::InProductMarketing.for(@track).new(group: group, user: current_user, series: @series) data = { namespace_id: group.id, @@ -58,8 +58,9 @@ class Groups::EmailCampaignsController < Groups::ApplicationController @series = params[:series]&.to_i track_valid = @track.in?(Namespaces::InProductMarketingEmailsService::TRACKS.keys) - series_valid = @series.in?(0..Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.size - 1) + return render_404 unless track_valid - render_404 unless track_valid && series_valid + series_valid = @series.in?(0..Namespaces::InProductMarketingEmailsService::TRACKS[@track][:interval_days].size - 1) + render_404 unless series_valid end end diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb index 137f830e40b..a6415daef68 100644 --- a/app/controllers/jira_connect/app_descriptor_controller.rb +++ b/app/controllers/jira_connect/app_descriptor_controller.rb @@ -39,7 +39,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController private HOME_URL = 'https://gitlab.com' - DOC_URL = 'https://docs.gitlab.com/ee/user/project/integrations/jira.html#gitlab-jira-integration' + DOC_URL = 'https://docs.gitlab.com/ee/integration/jira/' def modules modules = { diff --git a/app/graphql/types/snippets/visibility_scopes_enum.rb b/app/graphql/types/snippets/visibility_scopes_enum.rb index 5488e05b95d..ddcc005eaf2 100644 --- a/app/graphql/types/snippets/visibility_scopes_enum.rb +++ b/app/graphql/types/snippets/visibility_scopes_enum.rb @@ -3,9 +3,9 @@ module Types module Snippets class VisibilityScopesEnum < BaseEnum - value 'private', value: 'are_private' - value 'internal', value: 'are_internal' - value 'public', value: 'are_public' + value 'private', description: 'The snippet is visible only to the snippet creator.', value: 'are_private' + value 'internal', description: 'The snippet is visible for any logged in user except external users.', value: 'are_internal' + value 'public', description: 'The snippet can be accessed without any authentication.', value: 'are_public' end end end diff --git a/app/helpers/admin/background_migrations_helper.rb b/app/helpers/admin/background_migrations_helper.rb new file mode 100644 index 00000000000..698d81cc8a2 --- /dev/null +++ b/app/helpers/admin/background_migrations_helper.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Admin + module BackgroundMigrationsHelper + def batched_migration_status_badge_class_name(migration) + class_names = { + 'active' => 'badge-info', + 'paused' => 'badge-warning', + 'failed' => 'badge-danger', + 'finished' => 'badge-success' + } + + class_names[migration.status] + end + + # The extra logic here is needed because total_tuple_count is just + # an estimate and completed_rows also does not account for last jobs + # whose batch size is likely larger than the actual number of rows processed + def batched_migration_progress(migration, completed_rows) + return 100 if migration.finished? + return 0 unless completed_rows.to_i > 0 + return unless migration.total_tuple_count.to_i > 0 + + [100 * completed_rows / migration.total_tuple_count, 99].min + end + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 1449725fb2b..80b8b5922e6 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -197,7 +197,7 @@ module IssuesHelper initial_email: project.new_issuable_address(current_user, 'issue'), is_signed_in: current_user.present?.to_s, issues_path: project_issues_path(project), - jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), + jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'), markdown_help_path: help_page_path('user/markdown'), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }), diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index aab1a44bdfb..f03c4356015 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -61,7 +61,7 @@ module NavHelper end def admin_monitoring_nav_links - %w(system_info background_jobs health_check requests_profiles) + %w(system_info background_migrations background_jobs health_check requests_profiles) end def admin_analytics_nav_links diff --git a/app/mailers/emails/in_product_marketing.rb b/app/mailers/emails/in_product_marketing.rb index 97243660512..e745cd51a55 100644 --- a/app/mailers/emails/in_product_marketing.rb +++ b/app/mailers/emails/in_product_marketing.rb @@ -14,8 +14,9 @@ module Emails def in_product_marketing_email(recipient_id, group_id, track, series) group = Group.find(group_id) - email = User.find(recipient_id).notification_email_for(group) - @message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, series: series) + user = User.find(recipient_id) + email = user.notification_email_for(group) + @message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, user: user, series: series) mail_to(to: email, subject: @message.subject_line) end diff --git a/app/models/onboarding_progress.rb b/app/models/onboarding_progress.rb index be76c3dbf9d..9185547d7cd 100644 --- a/app/models/onboarding_progress.rb +++ b/app/models/onboarding_progress.rb @@ -85,6 +85,10 @@ class OnboardingProgress < ApplicationRecord end end + def number_of_completed_actions + attributes.extract!(*ACTIONS.map { |action| self.class.column_name(action).to_s }).compact!.size + end + private def namespace_is_root_namespace diff --git a/app/models/users/in_product_marketing_email.rb b/app/models/users/in_product_marketing_email.rb index 195cfe162ac..3e5e7b259d8 100644 --- a/app/models/users/in_product_marketing_email.rb +++ b/app/models/users/in_product_marketing_email.rb @@ -18,7 +18,8 @@ module Users create: 0, verify: 1, trial: 2, - team: 3 + team: 3, + experience: 4 }, _suffix: true scope :without_track_and_series, -> (track, series) do diff --git a/app/services/namespaces/in_product_marketing_emails_service.rb b/app/services/namespaces/in_product_marketing_emails_service.rb index 61d5ed3bdf4..3461362b48c 100644 --- a/app/services/namespaces/in_product_marketing_emails_service.rb +++ b/app/services/namespaces/in_product_marketing_emails_service.rb @@ -4,17 +4,37 @@ module Namespaces class InProductMarketingEmailsService include Gitlab::Experimentation::GroupTypes - INTERVAL_DAYS = [1, 5, 10].freeze TRACKS = { - create: :git_write, - verify: :pipeline_created, - trial: :trial_started, - team: :user_added + create: { + interval_days: [1, 5, 10], + completed_actions: [:created], + incomplete_actions: [:git_write] + }, + verify: { + interval_days: [1, 5, 10], + completed_actions: [:git_write], + incomplete_actions: [:pipeline_created] + }, + trial: { + interval_days: [1, 5, 10], + completed_actions: [:git_write, :pipeline_created], + incomplete_actions: [:trial_started] + }, + team: { + interval_days: [1, 5, 10], + completed_actions: [:git_write, :pipeline_created, :trial_started], + incomplete_actions: [:user_added] + }, + experience: { + interval_days: [30], + completed_actions: [:created, :git_write], + incomplete_actions: [] + } }.freeze def self.send_for_all_tracks_and_intervals TRACKS.each_key do |track| - INTERVAL_DAYS.each do |interval| + TRACKS[track][:interval_days].each do |interval| new(track, interval).execute end end @@ -69,7 +89,7 @@ module Namespaces def groups_for_track onboarding_progress_scope = OnboardingProgress .completed_actions_with_latest_in_range(completed_actions, range) - .incomplete_actions(incomplete_action) + .incomplete_actions(incomplete_actions) # Filtering out sub-groups is a temporary fix to prevent calling # `.root_ancestor` on groups that are not root groups. @@ -103,6 +123,8 @@ module Namespaces user.can?(:start_trial, group) when :team user.can?(:admin_group_member, group) + when :experience + true end end @@ -111,8 +133,7 @@ module Namespaces end def completed_actions - index = TRACKS.keys.index(track) - index == 0 ? [:created] : TRACKS.values[0..index - 1] + TRACKS[track][:completed_actions] end def range @@ -120,12 +141,12 @@ module Namespaces date.beginning_of_day..date.end_of_day end - def incomplete_action - TRACKS[track] + def incomplete_actions + TRACKS[track][:incomplete_actions] end def series - INTERVAL_DAYS.index(interval) + TRACKS[track][:interval_days].index(interval) end def save_tracked_emails! diff --git a/app/views/admin/background_migrations/_migration.html.haml b/app/views/admin/background_migrations/_migration.html.haml new file mode 100644 index 00000000000..40860ea9400 --- /dev/null +++ b/app/views/admin/background_migrations/_migration.html.haml @@ -0,0 +1,10 @@ +%tr{ role: 'row' } + %td{ role: 'cell', data: { label: _('Migration') } }= migration.job_class_name + ': ' + migration.table_name + %td{ role: 'cell', data: { label: _('Progress') } } + - progress = batched_migration_progress(migration, @successful_rows_counts[migration.id]) + - if progress + = number_to_percentage(progress, precision: 2) + - else + = _('Unknown') + %td{ role: 'cell', data: { label: _('Status') } } + %span.badge.badge-pill.gl-badge.sm{ class: batched_migration_status_badge_class_name(migration) }= migration.status.humanize diff --git a/app/views/admin/background_migrations/index.html.haml b/app/views/admin/background_migrations/index.html.haml new file mode 100644 index 00000000000..2a372c89912 --- /dev/null +++ b/app/views/admin/background_migrations/index.html.haml @@ -0,0 +1,35 @@ +- page_title _('Background Migrations') + +.tabs.gl-tabs + %div + %ul.nav.gl-tabs-nav{ role: 'tablist' } + - active_tab_classes = ['gl-tab-nav-item-active', 'gl-tab-nav-item-active-indigo'] + + %li.nav-item{ role: 'presentation' } + %a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path, class: (active_tab_classes if @current_tab == 'queued'), role: 'tab' } + = _('Queued') + %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm + = limited_counter_with_delimiter(@relations_by_tab['queued']) + %li.nav-item{ role: 'presentation' } + %a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path(tab: 'failed'), class: (active_tab_classes if @current_tab == 'failed'), role: 'tab' } + = _('Failed') + %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm + = limited_counter_with_delimiter(@relations_by_tab['failed']) + %li.nav-item{ role: 'presentation' } + %a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path(tab: 'finished'), class: (active_tab_classes if @current_tab == 'finished'), role: 'tab' } + = _('Finished') + %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm + = limited_counter_with_delimiter(@relations_by_tab['finished']) + + .tab-content.gl-tab-content + .tab-pane.active{ role: 'tabpanel' } + %table.table.b-table.gl-table.b-table-stacked-md{ role: 'table' } + %thead{ role: 'rowgroup' } + %tr{ role: 'row' } + %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Migration') + %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Progress') + %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Status') + %tbody{ role: 'rowgroup' } + = render partial: 'migration', collection: @migrations + + = paginate_collection @migrations diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index b71866c9138..a7263ae2576 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -78,7 +78,7 @@ = _('Monitoring') %ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_sidebar_monitoring_submenu_content' } } - = nav_link(controller: %w(system_info background_jobs health_check requests_profiles), html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: admin_monitoring_nav_links, html_options: { class: "fly-out-top-item" } ) do = link_to admin_system_info_path do %strong.fly-out-top-item-name = _('Monitoring') @@ -87,6 +87,10 @@ = link_to admin_system_info_path, title: _('System Info') do %span = _('System Info') + = nav_link(controller: :background_migrations) do + = link_to admin_background_migrations_path, title: _('Background Migrations') do + %span + = _('Background Migrations') = nav_link(controller: :background_jobs) do = link_to admin_background_jobs_path, title: _('Background Jobs') do %span diff --git a/app/views/notify/in_product_marketing_email.html.haml b/app/views/notify/in_product_marketing_email.html.haml index a1c3ecfb87e..45b002757e3 100644 --- a/app/views/notify/in_product_marketing_email.html.haml +++ b/app/views/notify/in_product_marketing_email.html.haml @@ -184,9 +184,32 @@ - @message.body_line2&.tap do |line| %p{ style: "margin: 0 0 20px 0;" } = line.html_safe - %tr - %td{ align: "center", style: "padding: 10px 20px 80px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif;" } - .cta_link= @message.cta_link + - if @message.cta_text + %tr + %td{ align: "center", style: "padding: 10px 20px 80px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif;" } + .cta_link= @message.cta_link + - else + %tr + %td{ style: "padding: 10px 20px 10px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; color:#000000; font-size: 16px; line-height: 20px;" } + %table{ border: "0", cellpadding: "0", cellspacing: "0", width: "100%", style: "width: 100%; min-width: 100%;" } + %tr + %td{ width: "50%", style: "width: 50%; min-width: 50%; color: #000000; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; font-size: 16px; line-height: 100%; padding-bottom: 16px; text-align: left;", align: "left" } + = @message.feedback_ratings(1) + %td{ width: "50%", style: "width: 50%; min-width: 50%; color: #000000; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; font-size: 16px; line-height: 100%; padding-bottom: 16px; text-align: right;", align: "right" } + = @message.feedback_ratings(5) + %tr + %td{ align: "center", style: "padding: 10px 1px 30px 1px;" } + %table{ align: "center", cellpadding: "5", cellspacing: "0", width: "100%", style: "width: 100%; min-width: 100%; border: 1px solid #dae0ea; border-radius: 0; min-width: 100%; text-align: center; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; font-size: 16px;" } + %tr + - (1..5).each do |rating| + %td{ height: "54", style: "border-left: 1px solid #dae0ea; padding-bottom: 0; width: 9% !important;", width: "9%" } + %a{ href: @message.feedback_link(rating), style: "color: #424242; display: block; text-decoration: none;" } + %span{ height: "54", style: "display: block; font-size: 18px; height: 22px; line-height: 22px; padding: 16px 0; width: 100%; text-decoration: none;" } + = rating + %tr + %td{ style: "padding: 10px 20px 30px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; color:#000000; font-size: 18px; line-height: 24px;" } + %p{ style: "margin: 0 0 50px 0;" } + = @message.feedback_thanks %tr{ style: "background-color: #ffffff;" } %td{ align: "center", style: "padding:75px 20px 25px;" } = about_link('gitlab_logo.png', 80) diff --git a/app/views/notify/in_product_marketing_email.text.erb b/app/views/notify/in_product_marketing_email.text.erb index 7d0fe7aec6d..6f0a2efa410 100644 --- a/app/views/notify/in_product_marketing_email.text.erb +++ b/app/views/notify/in_product_marketing_email.text.erb @@ -8,10 +8,19 @@ <%= @message.body_line2 %> +<% if @message.cta_text %> <%= @message.cta_link %> +<% else %> +<% (1..5).each do |rating| %> +<%= "#{rating} - #{@message.feedback_ratings(rating).upcase} - #{@message.feedback_link(rating)}" %> +<% end %> + + +<%= @message.feedback_thanks %> +<% end %> diff --git a/changelogs/unreleased/326760-batched-migrations-admin-panel.yml b/changelogs/unreleased/326760-batched-migrations-admin-panel.yml new file mode 100644 index 00000000000..d24e92f3952 --- /dev/null +++ b/changelogs/unreleased/326760-batched-migrations-admin-panel.yml @@ -0,0 +1,5 @@ +--- +title: Add admin page for batched background migrations +merge_request: 60911 +author: +type: added diff --git a/changelogs/unreleased/326791-removing-redundant-update.yml b/changelogs/unreleased/326791-removing-redundant-update.yml new file mode 100644 index 00000000000..1e57b2f2517 --- /dev/null +++ b/changelogs/unreleased/326791-removing-redundant-update.yml @@ -0,0 +1,5 @@ +--- +title: Remove the redundant update for API endpoint projects/:id/statuses/:sha +merge_request: 61470 +author: +type: performance diff --git a/changelogs/unreleased/add-ease-score-onboarding-email.yml b/changelogs/unreleased/add-ease-score-onboarding-email.yml new file mode 100644 index 00000000000..7be2a70f89c --- /dev/null +++ b/changelogs/unreleased/add-ease-score-onboarding-email.yml @@ -0,0 +1,5 @@ +--- +title: Add ease score onboarding in-product marketing email +merge_request: 61347 +author: +type: changed diff --git a/config/metrics/counts_28d/20210216183638_unique_users_all_imports.yml b/config/metrics/counts_28d/20210216183638_unique_users_all_imports.yml index 6651dfa0146..670b8d555d8 100644 --- a/config/metrics/counts_28d/20210216183638_unique_users_all_imports.yml +++ b/config/metrics/counts_28d/20210216183638_unique_users_all_imports.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.unique_users_all_imports -description: '' -product_section: '' -product_stage: '' -product_group: '' +description: Number of users from projects imported +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183644_gitlab_project.yml b/config/metrics/counts_28d/20210216183644_gitlab_project.yml index 166be23baac..03c2d993a6c 100644 --- a/config/metrics/counts_28d/20210216183644_gitlab_project.yml +++ b/config/metrics/counts_28d/20210216183644_gitlab_project.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.gitlab_project description: '' -product_section: '' -product_stage: '' -product_group: '' +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183646_gitlab.yml b/config/metrics/counts_28d/20210216183646_gitlab.yml index 25bb788cc4f..5cfad29d4a1 100644 --- a/config/metrics/counts_28d/20210216183646_gitlab.yml +++ b/config/metrics/counts_28d/20210216183646_gitlab.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.gitlab description: '' -product_section: '' -product_stage: '' -product_group: '' +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183648_github.yml b/config/metrics/counts_28d/20210216183648_github.yml index 6f79b49a37f..124ae696d68 100644 --- a/config/metrics/counts_28d/20210216183648_github.yml +++ b/config/metrics/counts_28d/20210216183648_github.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.github -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' +description: Count of projects imported from GitHub +product_section: dev +product_stage: manage +product_group: group::import +product_category: value_type: number status: data_available -time_frame: 28d -data_source: +time_frame: all +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183650_bitbucket.yml b/config/metrics/counts_28d/20210216183650_bitbucket.yml index fc31e325278..9d8b9374e3f 100644 --- a/config/metrics/counts_28d/20210216183650_bitbucket.yml +++ b/config/metrics/counts_28d/20210216183650_bitbucket.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.bitbucket -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' +description: Count of projects imported from Bitbucket +product_section: dev +product_stage: manage +product_group: group::import +product_category: value_type: number status: data_available -time_frame: 28d -data_source: +time_frame: all +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183652_bitbucket_server.yml b/config/metrics/counts_28d/20210216183652_bitbucket_server.yml index fd0b64933b1..e84adc3aba8 100644 --- a/config/metrics/counts_28d/20210216183652_bitbucket_server.yml +++ b/config/metrics/counts_28d/20210216183652_bitbucket_server.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.bitbucket_server -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' +description: Count of projects imported from Bitbucket Server +product_section: dev +product_stage: manage +product_group: group::import +product_category: value_type: number status: data_available -time_frame: 28d -data_source: +time_frame: all +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183653_gitea.yml b/config/metrics/counts_28d/20210216183653_gitea.yml index a09fdc9d610..46087d194da 100644 --- a/config/metrics/counts_28d/20210216183653_gitea.yml +++ b/config/metrics/counts_28d/20210216183653_gitea.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.gitea -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' +description: Count of projects imported from Gitea +product_section: dev +product_stage: manage +product_group: group::import +product_category: value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183655_git.yml b/config/metrics/counts_28d/20210216183655_git.yml index b17900f534d..e0f3a1da4e1 100644 --- a/config/metrics/counts_28d/20210216183655_git.yml +++ b/config/metrics/counts_28d/20210216183655_git.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.git -description: '' -product_section: '' -product_stage: '' -product_group: '' +description: Count of projects imported from Git +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183657_manifest.yml b/config/metrics/counts_28d/20210216183657_manifest.yml index 49fcefb3a45..3bc9b639a59 100644 --- a/config/metrics/counts_28d/20210216183657_manifest.yml +++ b/config/metrics/counts_28d/20210216183657_manifest.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.manifest description: '' -product_section: '' -product_stage: '' -product_group: '' +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183659_gitlab_migration.yml b/config/metrics/counts_28d/20210216183659_gitlab_migration.yml index 700fe4b4881..1304ea7b166 100644 --- a/config/metrics/counts_28d/20210216183659_gitlab_migration.yml +++ b/config/metrics/counts_28d/20210216183659_gitlab_migration.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.project_imports.gitlab_migration description: '' -product_section: '' -product_stage: '' -product_group: '' +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183709_group_import.yml b/config/metrics/counts_28d/20210216183709_group_import.yml index 9116a242811..eca3eada4d4 100644 --- a/config/metrics/counts_28d/20210216183709_group_import.yml +++ b/config/metrics/counts_28d/20210216183709_group_import.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.group_imports.group_import -description: '' -product_section: '' -product_stage: '' -product_group: '' +description: Number of group import states +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: -- ce + - ce + - ee tier: -- free -skip_validation: true + - free + - premium + - ultimate diff --git a/config/metrics/counts_28d/20210216183720_bitbucket.yml b/config/metrics/counts_28d/20210216183720_bitbucket.yml index e05c607f202..b3b34c95800 100644 --- a/config/metrics/counts_28d/20210216183720_bitbucket.yml +++ b/config/metrics/counts_28d/20210216183720_bitbucket.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.projects_imported.bitbucket -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' +description: Count of projects imported from Bitbucket +product_section: dev +product_stage: manage +product_group: group::import +product_category: value_type: number status: deprecated time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183722_bitbucket_server.yml b/config/metrics/counts_28d/20210216183722_bitbucket_server.yml index c7f2a18f1b1..1a8ace962e0 100644 --- a/config/metrics/counts_28d/20210216183722_bitbucket_server.yml +++ b/config/metrics/counts_28d/20210216183722_bitbucket_server.yml @@ -1,16 +1,18 @@ --- key_path: usage_activity_by_stage_monthly.manage.projects_imported.bitbucket_server -description: '' -product_section: '' -product_stage: '' -product_group: '' +description: Count of projects imported from Bitbucket Server +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: deprecated time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216183737_groups_imported.yml b/config/metrics/counts_28d/20210216183737_groups_imported.yml index 3b668e6cac6..10ffa325a11 100644 --- a/config/metrics/counts_28d/20210216183737_groups_imported.yml +++ b/config/metrics/counts_28d/20210216183737_groups_imported.yml @@ -1,9 +1,9 @@ --- key_path: usage_activity_by_stage_monthly.manage.groups_imported description: '' -product_section: '' -product_stage: '' -product_group: '' +product_section: dev +product_stage: manage +product_group: group::import product_category: '' value_type: number status: deprecated diff --git a/config/metrics/counts_all/20210216180638_gitlab_project.yml b/config/metrics/counts_all/20210216180638_gitlab_project.yml index a9d167f7363..624b004155d 100644 --- a/config/metrics/counts_all/20210216180638_gitlab_project.yml +++ b/config/metrics/counts_all/20210216180638_gitlab_project.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180639_gitlab.yml b/config/metrics/counts_all/20210216180639_gitlab.yml index 3fef2ce3b85..39a878962d2 100644 --- a/config/metrics/counts_all/20210216180639_gitlab.yml +++ b/config/metrics/counts_all/20210216180639_gitlab.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180641_github.yml b/config/metrics/counts_all/20210216180641_github.yml index 530e9b05e14..c7ce1a7f1b3 100644 --- a/config/metrics/counts_all/20210216180641_github.yml +++ b/config/metrics/counts_all/20210216180641_github.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180643_bitbucket.yml b/config/metrics/counts_all/20210216180643_bitbucket.yml index 6490c8ffb55..e05e4e526ff 100644 --- a/config/metrics/counts_all/20210216180643_bitbucket.yml +++ b/config/metrics/counts_all/20210216180643_bitbucket.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180645_bitbucket_server.yml b/config/metrics/counts_all/20210216180645_bitbucket_server.yml index bfbee0b3433..88f618ac228 100644 --- a/config/metrics/counts_all/20210216180645_bitbucket_server.yml +++ b/config/metrics/counts_all/20210216180645_bitbucket_server.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180647_gitea.yml b/config/metrics/counts_all/20210216180647_gitea.yml index 74b4fd36714..d70b478659d 100644 --- a/config/metrics/counts_all/20210216180647_gitea.yml +++ b/config/metrics/counts_all/20210216180647_gitea.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180649_git.yml b/config/metrics/counts_all/20210216180649_git.yml index b7dd7ca8d7e..19c19e6cc63 100644 --- a/config/metrics/counts_all/20210216180649_git.yml +++ b/config/metrics/counts_all/20210216180649_git.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180650_manifest.yml b/config/metrics/counts_all/20210216180650_manifest.yml index 4ef7823ee67..6c89c68b34c 100644 --- a/config/metrics/counts_all/20210216180650_manifest.yml +++ b/config/metrics/counts_all/20210216180650_manifest.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216180652_gitlab_migration.yml b/config/metrics/counts_all/20210216180652_gitlab_migration.yml index 3df4472c9a7..d1c84d6c795 100644 --- a/config/metrics/counts_all/20210216180652_gitlab_migration.yml +++ b/config/metrics/counts_all/20210216180652_gitlab_migration.yml @@ -8,9 +8,11 @@ product_category: value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181259_jira_imports_projects_count.yml b/config/metrics/counts_all/20210216181259_jira_imports_projects_count.yml index 321273f800e..df04f5d0669 100644 --- a/config/metrics/counts_all/20210216181259_jira_imports_projects_count.yml +++ b/config/metrics/counts_all/20210216181259_jira_imports_projects_count.yml @@ -15,4 +15,4 @@ distribution: tier: - free - premium -- ultimate
\ No newline at end of file +- ultimate diff --git a/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml b/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml new file mode 100644 index 00000000000..816fee89f4c --- /dev/null +++ b/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.in_product_marketing_email_experience_0_sent +name: "count_sent_first_email_of_the_experience_track_for_in_product_marketing_emails" +description: Total sent emails of the experience track's first email +product_section: +product_stage: growth +product_group: group::activation +product_category: onboarding +value_type: number +status: implemented +milestone: "13.12" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61347 +time_frame: all +data_source: database +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 2ba00e3bf66..e929bb297d3 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -95,6 +95,7 @@ namespace :admin do get :instance_review, to: 'instance_review#index' + resources :background_migrations, only: [:index] resource :health_check, controller: 'health_check', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 7f496e1aded..792e10e85b7 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -14668,9 +14668,9 @@ Possible states of a user. | Value | Description | | ----- | ----------- | -| <a id="visibilityscopesenuminternal"></a>`internal` | | -| <a id="visibilityscopesenumprivate"></a>`private` | | -| <a id="visibilityscopesenumpublic"></a>`public` | | +| <a id="visibilityscopesenuminternal"></a>`internal` | The snippet is visible for any logged in user except external users. | +| <a id="visibilityscopesenumprivate"></a>`private` | The snippet is visible only to the snippet creator. | +| <a id="visibilityscopesenumpublic"></a>`public` | The snippet can be accessed without any authentication. | ### `VulnerabilityDismissalReason` diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index 070429eafd5..01ca5da3039 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -16,7 +16,7 @@ Constants for snippet visibility levels are: | visibility | Description | | ---------- | ----------- | -| `private` | The snippet is visible only the snippet creator | +| `private` | The snippet is visible only to the snippet creator | | `internal` | The snippet is visible for any logged in user except [external users](../user/permissions.md#external-users) | | `public` | The snippet can be accessed without any authentication | diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 75d65f8e5df..083520eccd3 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -2446,6 +2446,18 @@ Status: `implemented` Tiers: `free`, `premium`, `ultimate` +### `counts.in_product_marketing_email_experience_0_sent` + +Total sent emails of the experience track's first email + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml) + +Group: `group::activation` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + ### `counts.in_review_folder` Missing description @@ -16366,7 +16378,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.bitbucket_server` @@ -16378,7 +16390,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.git` @@ -16390,7 +16402,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.gitea` @@ -16402,7 +16414,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.github` @@ -16414,7 +16426,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.gitlab` @@ -16426,7 +16438,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.gitlab_migration` @@ -16438,7 +16450,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.gitlab_project` @@ -16450,7 +16462,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.project_imports.manifest` @@ -16462,7 +16474,7 @@ Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.manage.projects_imported.bitbucket` @@ -18088,15 +18100,15 @@ Tiers: `free` ### `usage_activity_by_stage_monthly.manage.group_imports.group_import` -Missing description +Number of group import states [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183709_group_import.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.group_saml_enabled` @@ -18128,7 +18140,7 @@ Missing description [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183737_groups_imported.yml) -Group: `` +Group: `group::import` Status: `deprecated` @@ -18304,63 +18316,63 @@ Tiers: `free` ### `usage_activity_by_stage_monthly.manage.project_imports.bitbucket` -Missing description +Count of projects imported from Bitbucket [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183650_bitbucket.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.bitbucket_server` -Missing description +Count of projects imported from Bitbucket Server [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183652_bitbucket_server.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.git` -Missing description +Count of projects imported from Git [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183655_git.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.gitea` -Missing description +Count of projects imported from Gitea [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183653_gitea.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.github` -Missing description +Count of projects imported from GitHub [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183648_github.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.gitlab` @@ -18368,11 +18380,11 @@ Missing description [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183646_gitlab.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.gitlab_migration` @@ -18380,11 +18392,11 @@ Missing description [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183659_gitlab_migration.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.gitlab_project` @@ -18392,11 +18404,11 @@ Missing description [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183644_gitlab_project.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.project_imports.manifest` @@ -18404,35 +18416,35 @@ Missing description [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183657_manifest.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.projects_imported.bitbucket` -Missing description +Count of projects imported from Bitbucket [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183720_bitbucket.yml) -Group: `` +Group: `group::import` Status: `deprecated` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.projects_imported.bitbucket_server` -Missing description +Count of projects imported from Bitbucket Server [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183722_bitbucket_server.yml) -Group: `` +Group: `group::import` Status: `deprecated` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.projects_imported.git` @@ -18532,15 +18544,15 @@ Tiers: ### `usage_activity_by_stage_monthly.manage.unique_users_all_imports` -Missing description +Number of users from projects imported [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183638_unique_users_all_imports.yml) -Group: `` +Group: `group::import` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.manage.user_auth_by_provider.google_oauth2` diff --git a/doc/update/index.md b/doc/update/index.md index 4c4dfe79d03..693e5be38ed 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -342,10 +342,10 @@ possible. ## Version-specific upgrading instructions -Each month, a major or minor release of GitLab is published along with a +Each month, major, minor or patch releases of GitLab are published along with a [release post](https://about.gitlab.com/releases/categories/releases/). -You should check all the major and minor versions you're passing over. -At the end of those release posts, there are three sections to look for: +You should read the release posts for all versions you're passing over. +At the end of major and minor release posts, there are three sections to look for specifically: - Deprecations - Removals diff --git a/doc/user/analytics/ci_cd_analytics.md b/doc/user/analytics/ci_cd_analytics.md index 284e87e9b35..b666b388eb8 100644 --- a/doc/user/analytics/ci_cd_analytics.md +++ b/doc/user/analytics/ci_cd_analytics.md @@ -4,12 +4,11 @@ group: Release info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# CI/CD Analytics +# CI/CD Analytics **(FREE)** -## Pipeline success and duration charts **(FREE)** +## Pipeline success and duration charts -> - Introduced in GitLab 3.1.1 as Commit Stats, and later renamed to Pipeline Charts. -> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/38318) to CI/CD Analytics in GitLab 12.8. +> [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/38318) to CI/CD Analytics in GitLab 12.8. GitLab tracks the history of your pipeline successes and failures, as well as how long each pipeline ran. To view this information, go to **Analytics > CI/CD Analytics**. @@ -48,14 +47,14 @@ performance indicators for software development teams: The following table shows the supported metrics, at which level they are supported, and which GitLab version (API and UI) they were introduced: -| Metric | Level | API version | Chart (UI) version | Comments | -| --------------- | ----------- | --------------- | ---------- | ------- | -| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endpoint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. | -| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | | -| `lead_time_for_changes` | Project-level | [13.10+](../../api/dora/metrics.md) | [13.11+](#lead-time-charts) | Unit in seconds. Aggregation method is median. | -| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. | -| `change_failure_rate` | Project/Group-level | To be supported | To be supported | | -| `time_to_restore_service` | Project/Group-level | To be supported | To be supported | | +| Metric | Level | API version | Chart (UI) version | Comments | +|---------------------------|---------------------|--------------------------------------|---------------------------------------|-----------| +| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endpoint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. | +| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | | +| `lead_time_for_changes` | Project-level | [13.10+](../../api/dora/metrics.md) | [13.11+](#lead-time-charts) | Unit in seconds. Aggregation method is median. | +| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. | +| `change_failure_rate` | Project/Group-level | To be supported | To be supported | | +| `time_to_restore_service` | Project/Group-level | To be supported | To be supported | | ### Deployment frequency charts **(ULTIMATE)** diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index e199111c975..27fee7fdea2 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -96,10 +96,8 @@ module API protected: user_project.protected_for?(ref) ) - optional_attributes = - attributes_for_keys(%w[target_url description coverage]) - - status.update(optional_attributes) if optional_attributes.any? + updatable_optional_attributes = %w[target_url description coverage] + status.assign_attributes(attributes_for_keys(updatable_optional_attributes)) if status.valid? status.update_older_statuses_retried! if Feature.enabled?(:ci_fix_commit_status_retried, user_project, default_enabled: :yaml) diff --git a/lib/gitlab/database/background_migration/batched_job.rb b/lib/gitlab/database/background_migration/batched_job.rb index 869b97b8ac0..9a1dc4ee17d 100644 --- a/lib/gitlab/database/background_migration/batched_job.rb +++ b/lib/gitlab/database/background_migration/batched_job.rb @@ -30,7 +30,7 @@ module Gitlab scope :successful_in_execution_order, -> { where.not(finished_at: nil).succeeded.order(:finished_at) } - delegate :aborted?, :job_class, :table_name, :column_name, :job_arguments, + delegate :job_class, :table_name, :column_name, :job_arguments, to: :batched_migration, prefix: :migration attribute :pause_ms, :integer, default: 100 diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb index e85162f355e..bf666a24a5b 100644 --- a/lib/gitlab/database/background_migration/batched_migration.rb +++ b/lib/gitlab/database/background_migration/batched_migration.rb @@ -15,11 +15,11 @@ module Gitlab foreign_key: :batched_background_migration_id scope :queue_order, -> { order(id: :asc) } + scope :queued, -> { where(status: [:active, :paused]) } enum status: { paused: 0, active: 1, - aborted: 2, finished: 3, failed: 4 } @@ -30,6 +30,14 @@ module Gitlab active.queue_order.first end + def self.successful_rows_counts(migrations) + BatchedJob + .succeeded + .where(batched_background_migration_id: migrations) + .group(:batched_background_migration_id) + .sum(:batch_size) + end + def interval_elapsed?(variance: 0) return true unless last_job diff --git a/lib/gitlab/email/message/in_product_marketing.rb b/lib/gitlab/email/message/in_product_marketing.rb index d538238f26f..fb4315e74b2 100644 --- a/lib/gitlab/email/message/in_product_marketing.rb +++ b/lib/gitlab/email/message/in_product_marketing.rb @@ -6,10 +6,8 @@ module Gitlab module InProductMarketing UnknownTrackError = Class.new(StandardError) - TRACKS = [:create, :verify, :team, :trial].freeze - def self.for(track) - raise UnknownTrackError unless TRACKS.include?(track) + raise UnknownTrackError unless Namespaces::InProductMarketingEmailsService::TRACKS.key?(track) "Gitlab::Email::Message::InProductMarketing::#{track.to_s.classify}".constantize end diff --git a/lib/gitlab/email/message/in_product_marketing/base.rb b/lib/gitlab/email/message/in_product_marketing/base.rb index 6341a7c7596..89acc058a46 100644 --- a/lib/gitlab/email/message/in_product_marketing/base.rb +++ b/lib/gitlab/email/message/in_product_marketing/base.rb @@ -10,10 +10,11 @@ module Gitlab attr_accessor :format - def initialize(group:, series:, format: :html) + def initialize(group:, user:, series:, format: :html) raise ArgumentError, "Only #{total_series} series available for this track." unless series.between?(0, total_series - 1) @group = group + @user = user @series = series @format = format end @@ -103,11 +104,7 @@ module Gitlab protected - attr_reader :group, :series - - def total_series - 3 - end + attr_reader :group, :user, :series private @@ -115,6 +112,10 @@ module Gitlab self.class.name.demodulize.downcase.to_sym end + def total_series + Namespaces::InProductMarketingEmailsService::TRACKS[track][:interval_days].size + end + def unsubscribe_com [ s_('InProductMarketing|If you no longer wish to receive marketing emails from us,'), diff --git a/lib/gitlab/email/message/in_product_marketing/experience.rb b/lib/gitlab/email/message/in_product_marketing/experience.rb new file mode 100644 index 00000000000..440c1029c09 --- /dev/null +++ b/lib/gitlab/email/message/in_product_marketing/experience.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Gitlab + module Email + module Message + module InProductMarketing + class Experience < Base + include Gitlab::Utils::StrongMemoize + + EASE_SCORE_SURVEY_ID = 1 + + def subject_line + s_('InProductMarketing|Do you have a minute?') + end + + def tagline + end + + def title + s_('InProductMarketing|We want your GitLab experience to be great') + end + + def subtitle + s_('InProductMarketing|Take this 1-question survey!') + end + + def body_line1 + s_('InProductMarketing|%{strong_start}Overall, how difficult or easy was it to get started with GitLab?%{strong_end}').html_safe % strong_options + end + + def body_line2 + s_('InProductMarketing|Click on the number below that corresponds with your answer — 1 being very difficult, 5 being very easy.') + end + + def cta_text + end + + def feedback_link(rating) + params = { + onboarding_progress: onboarding_progress, + response: rating, + show_invite_link: show_invite_link, + survey_id: EASE_SCORE_SURVEY_ID + } + + "#{Gitlab::COM_URL}/-/survey_responses?#{params.to_query}" + end + + def feedback_ratings(rating) + [ + s_('InProductMarketing|Very difficult'), + s_('InProductMarketing|Difficult'), + s_('InProductMarketing|Neutral'), + s_('InProductMarketing|Easy'), + s_('InProductMarketing|Very easy') + ][rating - 1] + end + + def feedback_thanks + s_('InProductMarketing|Feedback from users like you really improves our product. Thanks for your help!') + end + + private + + def onboarding_progress + strong_memoize(:onboarding_progress) do + group.onboarding_progress.number_of_completed_actions + end + end + + def show_invite_link + strong_memoize(:show_invite_link) do + group.member_count > 1 && group.max_member_access_for_user(user) >= GroupMember::DEVELOPER && user.preferred_language == 'en' + end + end + end + end + end + end +end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 1fd210c521e..14f9c7f2191 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -47,6 +47,7 @@ module Gitlab push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false) push_frontend_feature_flag(:usage_data_api, type: :ops, default_enabled: :yaml) push_frontend_feature_flag(:security_auto_fix, default_enabled: false) + push_frontend_feature_flag(:improved_emoji_picker, default_enabled: :yaml) end # Exposes the state of a feature flag to the frontend code. diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index b1ba529d4a4..f8441ad296c 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -852,17 +852,16 @@ module Gitlab sent_emails = count(Users::InProductMarketingEmail.group(:track, :series)) clicked_emails = count(Users::InProductMarketingEmail.where.not(cta_clicked_at: nil).group(:track, :series)) - series_amount = Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.count - Users::InProductMarketingEmail.tracks.keys.each_with_object({}) do |track, result| # rubocop: enable UsageData/LargeTable: + series_amount = Namespaces::InProductMarketingEmailsService::TRACKS[track.to_sym][:interval_days].count 0.upto(series_amount - 1).map do |series| # When there is an error with the query and it's not the Hash we expect, we return what we got from `count`. sent_count = sent_emails.is_a?(Hash) ? sent_emails.fetch([track, series], 0) : sent_emails clicked_count = clicked_emails.is_a?(Hash) ? clicked_emails.fetch([track, series], 0) : clicked_emails result["in_product_marketing_email_#{track}_#{series}_sent"] = sent_count - result["in_product_marketing_email_#{track}_#{series}_cta_clicked"] = clicked_count + result["in_product_marketing_email_#{track}_#{series}_cta_clicked"] = clicked_count unless track == 'experience' end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index af1d2ea1221..a1379ad2761 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4871,6 +4871,9 @@ msgstr "" msgid "Background Jobs" msgstr "" +msgid "Background Migrations" +msgstr "" + msgid "Background color" msgstr "" @@ -16914,6 +16917,9 @@ msgstr "" msgid "InProductMarketing|%{strong_start}Multiple approval roles%{strong_end} — including code owners and required merge approvals" msgstr "" +msgid "InProductMarketing|%{strong_start}Overall, how difficult or easy was it to get started with GitLab?%{strong_end}" +msgstr "" + msgid "InProductMarketing|*GitLab*, noun: a synonym for efficient teams" msgstr "" @@ -16947,6 +16953,9 @@ msgstr "" msgid "InProductMarketing|By enabling code owners and required merge approvals the right person will review the right MR. This is a win-win: cleaner code and a more efficient review process." msgstr "" +msgid "InProductMarketing|Click on the number below that corresponds with your answer — 1 being very difficult, 5 being very easy." +msgstr "" + msgid "InProductMarketing|Code owners and required merge approvals are part of the paid tiers of GitLab. You can start a free 30-day trial of GitLab Ultimate and enable these features in less than 5 minutes with no credit card required." msgstr "" @@ -16959,9 +16968,18 @@ msgstr "" msgid "InProductMarketing|Did you know teams that use GitLab are far more efficient?" msgstr "" +msgid "InProductMarketing|Difficult" +msgstr "" + msgid "InProductMarketing|Dig in and create a project and a repo" msgstr "" +msgid "InProductMarketing|Do you have a minute?" +msgstr "" + +msgid "InProductMarketing|Easy" +msgstr "" + msgid "InProductMarketing|Explore GitLab CI/CD" msgstr "" @@ -16974,6 +16992,9 @@ msgstr "" msgid "InProductMarketing|Facebook" msgstr "" +msgid "InProductMarketing|Feedback from users like you really improves our product. Thanks for your help!" +msgstr "" + msgid "InProductMarketing|Feel the need for speed?" msgstr "" @@ -17100,6 +17121,9 @@ msgstr "" msgid "InProductMarketing|Need an alternative to importing?" msgstr "" +msgid "InProductMarketing|Neutral" +msgstr "" + msgid "InProductMarketing|Our tool brings all the things together" msgstr "" @@ -17139,6 +17163,9 @@ msgstr "" msgid "InProductMarketing|Streamline code review, know at a glance who's unavailable, communicate in comments or in email and integrate with Slack so everyone's on the same page." msgstr "" +msgid "InProductMarketing|Take this 1-question survey!" +msgstr "" + msgid "InProductMarketing|Take your first steps with GitLab" msgstr "" @@ -17193,9 +17220,18 @@ msgstr "" msgid "InProductMarketing|Use GitLab CI/CD" msgstr "" +msgid "InProductMarketing|Very difficult" +msgstr "" + +msgid "InProductMarketing|Very easy" +msgstr "" + msgid "InProductMarketing|We know a thing or two about efficiency and we don't want to keep that to ourselves. Sign up for a free trial of GitLab Ultimate and your teams will be on it from day one." msgstr "" +msgid "InProductMarketing|We want your GitLab experience to be great" +msgstr "" + msgid "InProductMarketing|What does our value stream timeline look like from product to development to review and production?" msgstr "" @@ -21155,6 +21191,9 @@ msgstr "" msgid "Migrated %{success_count}/%{total_count} files." msgstr "" +msgid "Migration" +msgstr "" + msgid "Migration has been scheduled to be retried" msgstr "" diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index bdd442a1c8b..19df390a10f 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -58,15 +58,21 @@ module QA def comparable_group reload! if api_response.nil? - api_resource.except( - :id, - :web_url, - :visibility, - :full_name, - :full_path, - :created_at, - :parent_id, - :runners_token + api_resource.slice( + :name, + :path, + :description, + :emails_disabled, + :lfs_enabled, + :mentions_disabled, + :project_creation_level, + :request_access_enabled, + :require_two_factor_authentication, + :share_with_group_lock, + :subgroup_creation_level, + :two_factor_grace_perion + # TODO: Add back visibility comparison once https://gitlab.com/gitlab-org/gitlab/-/issues/331252 is fixed + # :visibility ) end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb index 055300122d4..ccf13ac01da 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb @@ -70,7 +70,9 @@ module QA it( 'performs bulk group import from another gitlab instance', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785', - exclude: { job: ['ce:relative_url', 'ee:relative_url'] } # https://gitlab.com/gitlab-org/gitlab/-/issues/330344 + exclude: { job: ['ce:relative_url', 'ee:relative_url'] }, + issue_1: "https://gitlab.com/gitlab-org/gitlab/-/issues/330344", + issue_2: "https://gitlab.com/gitlab-org/gitlab/-/issues/331252" ) do Page::Group::BulkImport.perform do |import_page| import_page.import_group(source_group.path, sandbox.path) diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f4f1e1bcbda..3cb18f21a47 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -242,6 +242,8 @@ FactoryBot.define do branch_name: evaluator.create_branch) end + + project.track_project_repository end end diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 787e0540fda..994bf066cf0 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -74,6 +74,7 @@ RSpec.describe 'factories' do milestone_release namespace project_broken_repo + project_repository prometheus_alert prometheus_alert_event prometheus_metric @@ -84,6 +85,7 @@ RSpec.describe 'factories' do release release_link self_managed_prometheus_alert_event + shard users_star_project wiki_page wiki_page_meta diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb new file mode 100644 index 00000000000..d848a8352bc --- /dev/null +++ b/spec/features/admin/admin_sees_background_migrations_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Admin > Admin sees background migrations" do + let_it_be(:admin) { create(:admin) } + + let_it_be(:active_migration) { create(:batched_background_migration, table_name: 'active', status: :active) } + let_it_be(:failed_migration) { create(:batched_background_migration, table_name: 'failed', status: :failed, total_tuple_count: 100) } + let_it_be(:finished_migration) { create(:batched_background_migration, table_name: 'finished', status: :finished) } + + before_all do + create(:batched_background_migration_job, batched_migration: failed_migration, batch_size: 30, status: :succeeded) + end + + before do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + end + + it 'can navigate to background migrations' do + visit admin_root_path + + within '.nav-sidebar' do + link = find_link 'Background Migrations' + + link.click + + expect(page).to have_current_path(admin_background_migrations_path) + expect(link).to have_ancestor(:css, 'li.active') + end + end + + it 'can view queued migrations' do + visit admin_background_migrations_path + + within '#content-body' do + expect(page).to have_selector('tbody tr', count: 1) + + expect(page).to have_content(active_migration.job_class_name) + expect(page).to have_content(active_migration.table_name) + expect(page).to have_content('0.00%') + expect(page).to have_content(active_migration.status.humanize) + end + end + + it 'can view failed migrations' do + visit admin_background_migrations_path + + within '#content-body' do + tab = find_link 'Failed' + tab.click + + expect(page).to have_current_path(admin_background_migrations_path(tab: 'failed')) + expect(tab[:class]).to include('gl-tab-nav-item-active', 'gl-tab-nav-item-active-indigo') + + expect(page).to have_selector('tbody tr', count: 1) + + expect(page).to have_content(failed_migration.job_class_name) + expect(page).to have_content(failed_migration.table_name) + expect(page).to have_content('30.00%') + expect(page).to have_content(failed_migration.status.humanize) + end + end + + it 'can view finished migrations' do + visit admin_background_migrations_path + + within '#content-body' do + tab = find_link 'Finished' + tab.click + + expect(page).to have_current_path(admin_background_migrations_path(tab: 'finished')) + expect(tab[:class]).to include('gl-tab-nav-item-active', 'gl-tab-nav-item-active-indigo') + + expect(page).to have_selector('tbody tr', count: 1) + + expect(page).to have_content(finished_migration.job_class_name) + expect(page).to have_content(finished_migration.table_name) + expect(page).to have_content('100.00%') + expect(page).to have_content(finished_migration.status.humanize) + end + end +end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index dddca15ae24..d941988d12f 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'User edit profile' do let(:user) { create(:user) } before do + stub_feature_flags(improved_emoji_picker: false) + sign_in(user) visit(profile_path) end diff --git a/spec/fixtures/product_intelligence/survey_response_schema.json b/spec/fixtures/product_intelligence/survey_response_schema.json index 11454116d83..03d2d170137 100644 --- a/spec/fixtures/product_intelligence/survey_response_schema.json +++ b/spec/fixtures/product_intelligence/survey_response_schema.json @@ -3,7 +3,7 @@ "self": { "vendor": "com.gitlab", "name": "survey_response", - "version": "1-0-0", + "version": "1-0-1", "format": "jsonschema" }, "type": "object", @@ -47,6 +47,12 @@ "description": "Username", "type": ["string", "null"], "maxLength": 255 + }, + "onboarding_progress": { + "description": "Onboarding progress", + "type": ["integer", "null"], + "minimum": 0, + "maximum": 2147483647 } } } diff --git a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js index 82fc06e1166..318c565f068 100644 --- a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js +++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js @@ -2,6 +2,7 @@ import { GlModal, GlFormCheckbox } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { initEmojiMock } from 'helpers/emoji'; import * as UserApi from '~/api/user_api'; +import EmojiPicker from '~/emoji/components/picker.vue'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import SetStatusModalWrapper, { AVAILABILITY_STATUS, @@ -25,7 +26,7 @@ describe('SetStatusModalWrapper', () => { defaultEmoji, }; - const createComponent = (props = {}) => { + const createComponent = (props = {}, improvedEmojiPicker = false) => { return shallowMount(SetStatusModalWrapper, { propsData: { ...defaultProps, @@ -34,6 +35,9 @@ describe('SetStatusModalWrapper', () => { mocks: { $toast, }, + provide: { + glFeatures: { improvedEmojiPicker }, + }, }); }; @@ -106,6 +110,20 @@ describe('SetStatusModalWrapper', () => { }); }); + describe('improvedEmojiPicker is true', () => { + beforeEach(async () => { + mockEmoji = await initEmojiMock(); + wrapper = createComponent({}, true); + return initModal(); + }); + + it('sets emojiTag when clicking in emoji picker', async () => { + await wrapper.findComponent(EmojiPicker).vm.$emit('click', 'thumbsup'); + + expect(wrapper.vm.emojiTag).toContain('data-name="thumbsup"'); + }); + }); + describe('with no currentMessage set', () => { beforeEach(async () => { mockEmoji = await initEmojiMock(); diff --git a/spec/helpers/admin/background_migrations_helper_spec.rb b/spec/helpers/admin/background_migrations_helper_spec.rb new file mode 100644 index 00000000000..8880a00755b --- /dev/null +++ b/spec/helpers/admin/background_migrations_helper_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Admin::BackgroundMigrationsHelper do + describe '#batched_migration_status_badge_class_name' do + using RSpec::Parameterized::TableSyntax + + where(:status, :class_name) do + :active | 'badge-info' + :paused | 'badge-warning' + :failed | 'badge-danger' + :finished | 'badge-success' + end + + subject { helper.batched_migration_status_badge_class_name(migration) } + + with_them do + let(:migration) { build(:batched_background_migration, status: status) } + + it { is_expected.to eq(class_name) } + end + end + + describe '#batched_migration_progress' do + subject { helper.batched_migration_progress(migration, completed_rows) } + + let(:migration) { build(:batched_background_migration, status: :active, total_tuple_count: 100) } + let(:completed_rows) { 25 } + + it 'returns completion percentage' do + expect(subject).to eq(25) + end + + context 'when migration is finished' do + let(:migration) { build(:batched_background_migration, status: :finished, total_tuple_count: nil) } + + it 'returns 100 percent' do + expect(subject).to eq(100) + end + end + + context 'when total_tuple_count is nil' do + let(:migration) { build(:batched_background_migration, status: :active, total_tuple_count: nil) } + + it 'returns nil' do + expect(subject).to eq(nil) + end + + context 'when there are no completed rows' do + let(:completed_rows) { 0 } + + it 'returns 0 percent' do + expect(subject).to eq(0) + end + end + end + + context 'when completed rows are greater than total count' do + let(:completed_rows) { 150 } + + it 'returns 99 percent' do + expect(subject).to eq(99) + end + end + end +end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 17e6c75ca27..17fe90c830e 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -309,7 +309,7 @@ RSpec.describe IssuesHelper do initial_email: project.new_issuable_address(current_user, 'issue'), is_signed_in: current_user.present?.to_s, issues_path: project_issues_path(project), - jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), + jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'), markdown_help_path: help_page_path('user/markdown'), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }), diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index 78e0b7627e9..2de784d3e16 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -49,16 +49,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d let(:batched_job) { build(:batched_background_migration_job) } let(:batched_migration) { batched_job.batched_migration } - describe '#migration_aborted?' do - before do - batched_migration.status = :aborted - end - - it 'returns the migration aborted?' do - expect(batched_job.migration_aborted?).to eq(batched_migration.aborted?) - end - end - describe '#migration_job_class' do it 'returns the migration job_class' do expect(batched_job.migration_job_class).to eq(batched_migration.job_class) diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index 43e34325419..e3507df5736 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -36,6 +36,38 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m it 'returns the first active migration according to queue order' do expect(described_class.active_migration).to eq(migration2) + create(:batched_background_migration_job, batched_migration: migration1, batch_size: 1000, status: :succeeded) + end + end + + describe '.queued' do + let!(:migration1) { create(:batched_background_migration, :finished) } + let!(:migration2) { create(:batched_background_migration, :paused) } + let!(:migration3) { create(:batched_background_migration, :active) } + + it 'returns active and paused migrations' do + expect(described_class.queued).to contain_exactly(migration2, migration3) + end + end + + describe '.successful_rows_counts' do + let!(:migration1) { create(:batched_background_migration) } + let!(:migration2) { create(:batched_background_migration) } + let!(:migration_without_jobs) { create(:batched_background_migration) } + + before do + create(:batched_background_migration_job, batched_migration: migration1, batch_size: 1000, status: :succeeded) + create(:batched_background_migration_job, batched_migration: migration1, batch_size: 200, status: :failed) + create(:batched_background_migration_job, batched_migration: migration2, batch_size: 500, status: :succeeded) + create(:batched_background_migration_job, batched_migration: migration2, batch_size: 200, status: :running) + end + + it 'returns totals from successful jobs' do + results = described_class.successful_rows_counts([migration1, migration2, migration_without_jobs]) + + expect(results[migration1.id]).to eq(1000) + expect(results[migration2.id]).to eq(500) + expect(results[migration_without_jobs.id]).to eq(nil) end end diff --git a/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb index 42d84b3e4de..277f1158f8b 100644 --- a/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb +++ b/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb @@ -4,12 +4,13 @@ require 'spec_helper' RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do let_it_be(:group) { build(:group) } + let_it_be(:user) { build(:user) } let(:series) { 0 } let(:test_class) { Gitlab::Email::Message::InProductMarketing::Create } describe 'initialize' do - subject { test_class.new(group: group, series: series) } + subject { test_class.new(group: group, user: user, series: series) } context 'when series does not exist' do let(:series) { 3 } @@ -29,13 +30,13 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do end describe '#logo_path' do - subject { test_class.new(group: group, series: series).logo_path } + subject { test_class.new(group: group, user: user, series: series).logo_path } it { is_expected.to eq('mailers/in_product_marketing/create-0.png') } end describe '#unsubscribe' do - subject { test_class.new(group: group, series: series).unsubscribe } + subject { test_class.new(group: group, user: user, series: series).unsubscribe } before do allow(Gitlab).to receive(:com?).and_return(is_gitlab_com) @@ -55,7 +56,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do end describe '#cta_link' do - subject(:cta_link) { test_class.new(group: group, series: series).cta_link } + subject(:cta_link) { test_class.new(group: group, user: user, series: series).cta_link } it 'renders link' do expect(CGI.unescapeHTML(cta_link)).to include(Gitlab::Routing.url_helpers.group_email_campaigns_url(group, track: :create, series: series)) @@ -63,7 +64,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do end describe '#progress' do - subject { test_class.new(group: group, series: series).progress } + subject { test_class.new(group: group, user: user, series: series).progress } before do allow(Gitlab).to receive(:com?).and_return(is_gitlab_com) diff --git a/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb index be8a33b18bd..35470ef3555 100644 --- a/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb +++ b/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb @@ -6,8 +6,9 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Create do using RSpec::Parameterized::TableSyntax let_it_be(:group) { build(:group) } + let_it_be(:user) { build(:user) } - subject(:message) { described_class.new(group: group, series: series)} + subject(:message) { described_class.new(group: group, user: user, series: series)} describe "public methods" do where(series: [0, 1, 2]) diff --git a/spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb new file mode 100644 index 00000000000..b742eff3f56 --- /dev/null +++ b/spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Email::Message::InProductMarketing::Experience do + let_it_be(:group) { build(:group) } + let_it_be(:user) { build(:user) } + + subject(:message) { described_class.new(group: group, user: user, series: series)} + + describe 'public methods' do + context 'with series 0' do + let(:series) { 0 } + + it 'returns value for series', :aggregate_failures do + expect(message.subject_line).to be_present + expect(message.tagline).to be_nil + expect(message.title).to be_present + expect(message.subtitle).to be_present + expect(message.body_line1).to be_present + expect(message.body_line2).to be_present + expect(message.cta_text).to be_nil + end + + describe '#feedback_link' do + let(:member_count) { 2 } + let(:user_access) { GroupMember::DEVELOPER } + let(:preferred_language) { 'en' } + + before do + allow(message).to receive(:onboarding_progress).and_return(1) + allow(group).to receive(:member_count).and_return(member_count) + allow(group).to receive(:max_member_access_for_user).and_return(user_access) + allow(user).to receive(:preferred_language).and_return(preferred_language) + end + + subject do + uri = URI.parse(message.feedback_link(1)) + Rack::Utils.parse_query(uri.query).with_indifferent_access[:show_invite_link] + end + + it { is_expected.to eq('true') } + + context 'with only one member' do + let(:member_count) { 1 } + + it { is_expected.to eq('false') } + end + + context 'with less than developer access' do + let(:user_access) { GroupMember::GUEST } + + it { is_expected.to eq('false') } + end + + context 'with preferred language other than English' do + let(:preferred_language) { 'nl' } + + it { is_expected.to eq('false') } + end + end + end + end +end diff --git a/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb index 6251128f560..f72994fcce1 100644 --- a/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb +++ b/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb @@ -6,8 +6,9 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Team do using RSpec::Parameterized::TableSyntax let_it_be(:group) { build(:group) } + let_it_be(:user) { build(:user) } - subject(:message) { described_class.new(group: group, series: series)} + subject(:message) { described_class.new(group: group, user: user, series: series)} describe "public methods" do where(series: [0, 1]) diff --git a/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb index 2c435490765..5f7639a9ed6 100644 --- a/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb +++ b/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb @@ -6,8 +6,9 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Trial do using RSpec::Parameterized::TableSyntax let_it_be(:group) { build(:group) } + let_it_be(:user) { build(:user) } - subject(:message) { described_class.new(group: group, series: series)} + subject(:message) { described_class.new(group: group, user: user, series: series)} describe "public methods" do where(series: [0, 1, 2]) diff --git a/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb index 73252c0dbdf..a7da2e9553d 100644 --- a/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb +++ b/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb @@ -4,8 +4,9 @@ require 'spec_helper' RSpec.describe Gitlab::Email::Message::InProductMarketing::Verify do let_it_be(:group) { build(:group) } + let_it_be(:user) { build(:user) } - subject(:message) { described_class.new(group: group, series: series)} + subject(:message) { described_class.new(group: group, user: user, series: series)} describe "public methods" do context 'with series 0' do diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index d4b6ac09261..c91f567c3c9 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -1499,7 +1499,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do "in_product_marketing_email_team_1_sent" => -1, "in_product_marketing_email_team_1_cta_clicked" => -1, "in_product_marketing_email_team_2_sent" => -1, - "in_product_marketing_email_team_2_cta_clicked" => -1 + "in_product_marketing_email_team_2_cta_clicked" => -1, + "in_product_marketing_email_experience_0_sent" => -1 } expect(subject).to eq(expected_data) @@ -1537,7 +1538,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do "in_product_marketing_email_team_1_sent" => 0, "in_product_marketing_email_team_1_cta_clicked" => 0, "in_product_marketing_email_team_2_sent" => 0, - "in_product_marketing_email_team_2_cta_clicked" => 0 + "in_product_marketing_email_team_2_cta_clicked" => 0, + "in_product_marketing_email_experience_0_sent" => 0 } expect(subject).to eq(expected_data) diff --git a/spec/mailers/emails/in_product_marketing_spec.rb b/spec/mailers/emails/in_product_marketing_spec.rb index 3d17e16ef48..74354630ade 100644 --- a/spec/mailers/emails/in_product_marketing_spec.rb +++ b/spec/mailers/emails/in_product_marketing_spec.rb @@ -9,6 +9,8 @@ RSpec.describe Emails::InProductMarketing do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } + let!(:onboarding_progress) { create(:onboarding_progress, namespace: group) } + describe '#in_product_marketing_email' do using RSpec::Parameterized::TableSyntax @@ -45,29 +47,35 @@ RSpec.describe Emails::InProductMarketing do end where(:track, :series) do - :create | 0 - :create | 1 - :create | 2 - :verify | 0 - :verify | 1 - :verify | 2 - :trial | 0 - :trial | 1 - :trial | 2 - :team | 0 - :team | 1 - :team | 2 + :create | 0 + :create | 1 + :create | 2 + :verify | 0 + :verify | 1 + :verify | 2 + :trial | 0 + :trial | 1 + :trial | 2 + :team | 0 + :team | 1 + :team | 2 + :experience | 0 end with_them do it 'has the correct subject and content' do - message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, series: series) + message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, user: user, series: series) aggregate_failures do is_expected.to have_subject(message.subject_line) is_expected.to have_body_text(message.title) is_expected.to have_body_text(message.subtitle) - is_expected.to have_body_text(CGI.unescapeHTML(message.cta_link)) + + if track == :experience + is_expected.to have_body_text(CGI.unescapeHTML(message.feedback_link(1))) + else + is_expected.to have_body_text(CGI.unescapeHTML(message.cta_link)) + end end end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 56afe49e15f..ec0507db1b3 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -804,9 +804,9 @@ RSpec.describe Namespace do end it 'updates the project storage location' do - repository_project_in_parent_group = create(:project_repository, project: project_in_parent_group) - repository_hashed_project_in_subgroup = create(:project_repository, project: hashed_project_in_subgroup) - repository_legacy_project_in_subgroup = create(:project_repository, project: legacy_project_in_subgroup) + repository_project_in_parent_group = project_in_parent_group.project_repository + repository_hashed_project_in_subgroup = hashed_project_in_subgroup.project_repository + repository_legacy_project_in_subgroup = legacy_project_in_subgroup.project_repository parent.update(path: 'mygroup_moved') diff --git a/spec/models/onboarding_progress_spec.rb b/spec/models/onboarding_progress_spec.rb index 779312c9fa0..deac8d29196 100644 --- a/spec/models/onboarding_progress_spec.rb +++ b/spec/models/onboarding_progress_spec.rb @@ -211,4 +211,26 @@ RSpec.describe OnboardingProgress do it { is_expected.to eq(:subscription_created_at) } end + + describe '#number_of_completed_actions' do + subject { build(:onboarding_progress, actions.map { |x| { x => Time.current } }.inject(:merge)).number_of_completed_actions } + + context '0 completed actions' do + let(:actions) { [:created_at, :updated_at] } + + it { is_expected.to eq(0) } + end + + context '1 completed action' do + let(:actions) { [:created_at, :subscription_created_at] } + + it { is_expected.to eq(1) } + end + + context '2 completed actions' do + let(:actions) { [:subscription_created_at, :git_write_at] } + + it { is_expected.to eq(2) } + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c57c2792f87..f7dace409aa 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2238,13 +2238,13 @@ RSpec.describe Project, factory_default: :keep do end context 'with projects on legacy storage' do - let(:project) { create(:project, :repository, :legacy_storage) } + let(:project) { create(:project, :legacy_storage) } it_behaves_like 'tracks storage location' end context 'with projects on hashed storage' do - let(:project) { create(:project, :repository) } + let(:project) { create(:project) } it_behaves_like 'tracks storage location' end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index ac125e81acd..ccc9f8c50c4 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' RSpec.describe API::CommitStatuses do - let!(:project) { create(:project, :repository) } - let(:commit) { project.repository.commit } - let(:guest) { create_user(:guest) } - let(:reporter) { create_user(:reporter) } - let(:developer) { create_user(:developer) } - let(:sha) { commit.id } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:commit) { project.repository.commit } + let_it_be(:guest) { create_user(:guest) } + let_it_be(:reporter) { create_user(:reporter) } + let_it_be(:developer) { create_user(:developer) } + let_it_be(:sha) { commit.id } describe "GET /projects/:id/repository/commits/:sha/statuses" do let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } @@ -233,27 +233,44 @@ RSpec.describe API::CommitStatuses do end end - context 'when updatig a commit status' do + context 'when updating a commit status' do + let(:parameters) do + { + state: 'success', + name: 'coverage', + ref: 'master' + } + end + + let(:updatable_optional_attributes) do + { + description: 'new description', + coverage: 90.0 + } + end + + # creating the initial commit status before do post api(post_url, developer), params: { state: 'running', context: 'coverage', ref: 'master', description: 'coverage test', - coverage: 0.0, + coverage: 10.0, target_url: 'http://gitlab.com/status' } + end + subject(:send_request) do post api(post_url, developer), params: { - state: 'success', - name: 'coverage', - ref: 'master', - description: 'new description', - coverage: 90.0 + **parameters, + **updatable_optional_attributes } end it 'updates a commit status' do + send_request + expect(response).to have_gitlab_http_status(:created) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') @@ -265,7 +282,28 @@ RSpec.describe API::CommitStatuses do end it 'does not create a new commit status' do - expect(CommitStatus.count).to eq 1 + expect { send_request }.not_to change { CommitStatus.count } + end + + context 'when the `state` parameter is sent the same' do + let(:parameters) do + { + state: 'running', + name: 'coverage', + ref: 'master' + } + end + + it 'does not update the commit status' do + send_request + + expect(response).to have_gitlab_http_status(:bad_request) + + commit_status = project.commit_statuses.find_by!(name: 'coverage') + + expect(commit_status.description).to eq('coverage test') + expect(commit_status.coverage).to eq(10.0) + end end end diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb index b40645ba2de..5b272121233 100644 --- a/spec/requests/api/project_repository_storage_moves_spec.rb +++ b/spec/requests/api/project_repository_storage_moves_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe API::ProjectRepositoryStorageMoves do it_behaves_like 'repository_storage_moves API', 'projects' do - let_it_be(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } + let_it_be(:container) { create(:project, :repository) } let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) } let(:repository_storage_move_factory) { :project_repository_storage_move } let(:bulk_worker_klass) { Projects::ScheduleBulkRepositoryShardMovesWorker } diff --git a/spec/requests/groups/email_campaigns_controller_spec.rb b/spec/requests/groups/email_campaigns_controller_spec.rb index 48297ec4cb6..290e6009ecb 100644 --- a/spec/requests/groups/email_campaigns_controller_spec.rb +++ b/spec/requests/groups/email_campaigns_controller_spec.rb @@ -9,10 +9,11 @@ RSpec.describe Groups::EmailCampaignsController do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } let_it_be(:user) { create(:user) } + let(:track) { 'create' } let(:series) { '0' } let(:schema) { described_class::EMAIL_CAMPAIGNS_SCHEMA_URL } - let(:subject_line_text) { Gitlab::Email::Message::InProductMarketing.for(track.to_sym).new(group: group, series: series.to_i).subject_line } + let(:subject_line_text) { Gitlab::Email::Message::InProductMarketing.for(track.to_sym).new(group: group, user: user, series: series.to_i).subject_line } let(:data) do { namespace_id: group.id, @@ -91,7 +92,7 @@ RSpec.describe Groups::EmailCampaignsController do describe 'track parameter' do context 'when valid' do - where(track: Namespaces::InProductMarketingEmailsService::TRACKS.keys) + where(track: Namespaces::InProductMarketingEmailsService::TRACKS.keys.without(:experience)) with_them do it_behaves_like 'track and redirect' @@ -109,7 +110,7 @@ RSpec.describe Groups::EmailCampaignsController do describe 'series parameter' do context 'when valid' do - where(series: (0..Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.length - 1).to_a) + where(series: (0..Namespaces::InProductMarketingEmailsService::TRACKS[:create][:interval_days].length - 1).to_a) with_them do it_behaves_like 'track and redirect' @@ -117,7 +118,7 @@ RSpec.describe Groups::EmailCampaignsController do end context 'when invalid' do - where(series: [-1, nil, Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.length]) + where(series: [-1, nil, Namespaces::InProductMarketingEmailsService::TRACKS[:create][:interval_days].length]) with_them do it_behaves_like 'no track and 404' diff --git a/spec/services/namespaces/in_product_marketing_emails_service_spec.rb b/spec/services/namespaces/in_product_marketing_emails_service_spec.rb index 3094f574184..2bf02e541f9 100644 --- a/spec/services/namespaces/in_product_marketing_emails_service_spec.rb +++ b/spec/services/namespaces/in_product_marketing_emails_service_spec.rb @@ -41,22 +41,23 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do using RSpec::Parameterized::TableSyntax where(:track, :interval, :actions_completed) do - :create | 1 | { created_at: frozen_time - 2.days } - :create | 5 | { created_at: frozen_time - 6.days } - :create | 10 | { created_at: frozen_time - 11.days } - :verify | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days } - :verify | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days } - :verify | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days } - :trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days } - :trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days } - :trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days } - :team | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days, trial_started_at: frozen_time - 2.days } - :team | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days, trial_started_at: frozen_time - 6.days } - :team | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days, trial_started_at: frozen_time - 11.days } + :create | 1 | { created_at: frozen_time - 2.days } + :create | 5 | { created_at: frozen_time - 6.days } + :create | 10 | { created_at: frozen_time - 11.days } + :verify | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days } + :verify | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days } + :verify | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days } + :trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days } + :trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days } + :trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days } + :team | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days, trial_started_at: frozen_time - 2.days } + :team | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days, trial_started_at: frozen_time - 6.days } + :team | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days, trial_started_at: frozen_time - 11.days } + :experience | 30 | { created_at: frozen_time - 31.days, git_write_at: frozen_time - 31.days } end with_them do - it { is_expected.to send_in_product_marketing_email(user.id, group.id, track, described_class::INTERVAL_DAYS.index(interval)) } + it { is_expected.to send_in_product_marketing_email(user.id, group.id, track, described_class::TRACKS[track][:interval_days].index(interval)) } end end @@ -235,7 +236,7 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do let(:track) { :foo } before do - stub_const("#{described_class}::TRACKS", { bar: :git_write }) + stub_const("#{described_class}::TRACKS", { bar: {} }) end it { expect { subject }.to raise_error(ArgumentError, 'Track foo not defined') } diff --git a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb index 2dc4a56368b..76830396104 100644 --- a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb +++ b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService do it_behaves_like 'moves repository shard in bulk' do - let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } + let_it_be_with_reload(:container) { create(:project, :repository) } let(:move_service_klass) { Projects::RepositoryStorageMove } let(:bulk_worker_klass) { ::Projects::ScheduleBulkRepositoryShardMovesWorker } diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 8498b752610..3171abfb36f 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -195,8 +195,6 @@ RSpec.describe Projects::TransferService do end it 'does not update storage location' do - create(:project_repository, project: project) - attempt_project_transfer expect(project.project_repository).to have_attributes( diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 8963164ac53..1737670071f 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -301,10 +301,8 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do shared_examples 'includes repositories in all repository storages' do specify :aggregate_failures do project_a = create(:project, :repository) - project_a.track_project_repository project_snippet_a = create(:project_snippet, :repository, project: project_a, author: project_a.owner) project_b = create(:project, :repository, repository_storage: second_storage_name) - project_b.track_project_repository project_snippet_b = create(:project_snippet, :repository, project: project_b, author: project_b.owner) project_snippet_b.snippet_repository.update!(shard: project_b.project_repository.shard) create(:wiki_page, container: project_a) diff --git a/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb b/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb index f284e1ab8c6..256f665c0c4 100644 --- a/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb +++ b/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe ProjectScheduleBulkRepositoryShardMovesWorker do it_behaves_like 'schedules bulk repository shard moves' do - let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } + let_it_be_with_reload(:container) { create(:project, :repository) } let(:move_service_klass) { Projects::RepositoryStorageMove } let(:worker_klass) { Projects::UpdateRepositoryStorageWorker } diff --git a/spec/workers/projects/schedule_bulk_repository_shard_moves_worker_spec.rb b/spec/workers/projects/schedule_bulk_repository_shard_moves_worker_spec.rb index 24957a35b72..7eff8e4dcd7 100644 --- a/spec/workers/projects/schedule_bulk_repository_shard_moves_worker_spec.rb +++ b/spec/workers/projects/schedule_bulk_repository_shard_moves_worker_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Projects::ScheduleBulkRepositoryShardMovesWorker do it_behaves_like 'schedules bulk repository shard moves' do - let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } + let_it_be_with_reload(:container) { create(:project, :repository) } let(:move_service_klass) { Projects::RepositoryStorageMove } let(:worker_klass) { Projects::UpdateRepositoryStorageWorker } |