diff options
58 files changed, 1554 insertions, 1074 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue index c805ec6883f..bd3d0c11022 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_table.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue @@ -168,7 +168,7 @@ export default { }; }, error() { - this.errored = true; + this.hasError = true; }, }, alertsCount: { @@ -187,10 +187,9 @@ export default { data() { return { searchTerm: '', - errored: false, + hasError: false, errorMessage: '', isAlertDismissed: false, - isErrorAlertDismissed: false, sort: 'STARTED_AT_DESC', statusFilter: [], filteredByStatus: '', @@ -203,16 +202,13 @@ export default { computed: { showNoAlertsMsg() { return ( - !this.errored && + !this.hasError && !this.loading && this.alertsCount?.all === 0 && !this.searchTerm && !this.isAlertDismissed ); }, - showErrorMsg() { - return this.errored && !this.isErrorAlertDismissed; - }, loading() { return this.$apollo.queries.alerts.loading; }, @@ -306,11 +302,11 @@ export default { }; }, handleAlertError(errorMessage) { - this.errored = true; + this.hasError = true; this.errorMessage = errorMessage; }, dismissError() { - this.isErrorAlertDismissed = true; + this.hasError = false; this.errorMessage = ''; }, }, @@ -332,12 +328,7 @@ export default { </template> </gl-sprintf> </gl-alert> - <gl-alert - v-if="showErrorMsg" - variant="danger" - data-testid="alert-error" - @dismiss="dismissError" - > + <gl-alert v-if="hasError" variant="danger" data-testid="alert-error" @dismiss="dismissError"> <p v-html="errorMessage || $options.i18n.errorMsg"></p> </gl-alert> diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue index 2731b49c4cc..66443a36b5c 100644 --- a/app/assets/javascripts/incidents/components/incidents_list.vue +++ b/app/assets/javascripts/incidents/components/incidents_list.vue @@ -11,13 +11,15 @@ import { GlSearchBoxByType, GlIcon, GlPagination, + GlTabs, + GlTab, } from '@gitlab/ui'; -import { debounce } from 'lodash'; +import { debounce, trim } from 'lodash'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { s__ } from '~/locale'; import { mergeUrlParams, joinPaths, visitUrl } from '~/lib/utils/url_utility'; import getIncidents from '../graphql/queries/get_incidents.query.graphql'; -import { I18N, DEFAULT_PAGE_SIZE, INCIDENT_SEARCH_DELAY } from '../constants'; +import { I18N, DEFAULT_PAGE_SIZE, INCIDENT_SEARCH_DELAY, INCIDENT_STATE_TABS } from '../constants'; const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap'; @@ -35,6 +37,7 @@ const initialPaginationState = { export default { i18n: I18N, + stateTabs: INCIDENT_STATE_TABS, fields: [ { key: 'title', @@ -67,6 +70,8 @@ export default { GlSearchBoxByType, GlIcon, GlPagination, + GlTabs, + GlTab, }, directives: { GlTooltip: GlTooltipDirective, @@ -78,6 +83,7 @@ export default { variables() { return { searchTerm: this.searchTerm, + state: this.stateFilter, projectPath: this.projectPath, labelNames: ['incident'], firstPageSize: this.pagination.firstPageSize, @@ -105,6 +111,7 @@ export default { searchTerm: '', pagination: initialPaginationState, incidents: {}, + stateFilter: '', }; }, computed: { @@ -138,14 +145,17 @@ export default { return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath); }, }, - watch: { - searchTerm: debounce(function debounceSearch(input) { - if (input !== this.searchTerm) { - this.searchTerm = input; + methods: { + onInputChange: debounce(function debounceSearch(input) { + const trimmedInput = trim(input); + if (trimmedInput !== this.searchTerm) { + this.searchTerm = trimmedInput; } }, INCIDENT_SEARCH_DELAY), - }, - methods: { + filterIncidentsByState(tabIndex) { + const { filters } = this.$options.stateTabs[tabIndex]; + this.stateFilter = filters; + }, hasAssignees(assignees) { return Boolean(assignees.nodes?.length); }, @@ -183,9 +193,17 @@ export default { {{ $options.i18n.errorMsg }} </gl-alert> - <div class="gl-display-flex gl-justify-content-end"> + <div class="incident-management-list-header gl-display-flex gl-justify-content-space-between"> + <gl-tabs content-class="gl-p-0" @input="filterIncidentsByState"> + <gl-tab v-for="tab in $options.stateTabs" :key="tab.state" :data-testid="tab.state"> + <template #title> + <span>{{ tab.title }}</span> + </template> + </gl-tab> + </gl-tabs> + <gl-button - class="gl-mt-3 gl-mb-3 create-incident-button" + class="gl-my-3 create-incident-button" data-testid="createIncidentBtn" :loading="redirecting" :disabled="redirecting" @@ -200,9 +218,10 @@ export default { <div class="gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100"> <gl-search-box-by-type - v-model.trim="searchTerm" + :value="searchTerm" class="gl-bg-white" :placeholder="$options.i18n.searchPlaceholder" + @input="onInputChange" /> </div> @@ -221,7 +240,7 @@ export default { @row-clicked="navigateToIncidentDetails" > <template #cell(title)="{ item }"> - <div class="gl-display-flex gl-justify-content-center"> + <div class="gl-display-sm-flex gl-align-items-center"> <div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div> <gl-icon v-if="item.state === 'closed'" diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js index 4246c3e914f..3a3efa98f25 100644 --- a/app/assets/javascripts/incidents/constants.js +++ b/app/assets/javascripts/incidents/constants.js @@ -8,5 +8,23 @@ export const I18N = { searchPlaceholder: __('Search or filter results...'), }; +export const INCIDENT_STATE_TABS = [ + { + title: s__('IncidentManagement|Open'), + state: 'OPENED', + filters: 'opened', + }, + { + title: s__('IncidentManagement|Closed'), + state: 'CLOSED', + filters: 'closed', + }, + { + title: s__('IncidentManagement|All incidents'), + state: 'ALL', + filters: 'all', + }, +]; + export const INCIDENT_SEARCH_DELAY = 300; export const DEFAULT_PAGE_SIZE = 10; diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss index 6070ab92974..c2ce2368705 100644 --- a/app/assets/stylesheets/fontawesome_custom.scss +++ b/app/assets/stylesheets/fontawesome_custom.scss @@ -80,31 +80,6 @@ } } -.fa-stack { - position: relative; - display: inline-block; - width: 2em; - height: 2em; - line-height: 2em; - vertical-align: middle; -} - -.fa-stack-1x, -.fa-stack-2x { - position: absolute; - left: 0; - width: 100%; - text-align: center; -} - -.fa-stack-1x { - line-height: inherit; -} - -.fa-stack-2x { - font-size: 2em; -} - .fa-inverse { color: $white; } @@ -256,10 +231,6 @@ content: '\f111'; } -.fa-certificate::before { - content: '\f0a3'; -} - .fa-bitbucket::before { content: '\f171'; } diff --git a/app/assets/stylesheets/pages/incident_management_list.scss b/app/assets/stylesheets/pages/incident_management_list.scss index 47a8238d3dd..e6305a5d233 100644 --- a/app/assets/stylesheets/pages/incident_management_list.scss +++ b/app/assets/stylesheets/pages/incident_management_list.scss @@ -90,6 +90,10 @@ } @include media-breakpoint-down(xs) { + .incident-management-list-header { + flex-direction: column-reverse; + }; + .create-incident-button { @include gl-w-full; } diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e79a0135d2f..24010b20db1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -36,7 +36,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:multiline_comments, @project) push_frontend_feature_flag(:file_identifier_hash) push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true) - push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project) + push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true) push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, @project) end diff --git a/app/graphql/mutations/concerns/mutations/resolves_subscription.rb b/app/graphql/mutations/concerns/mutations/resolves_subscription.rb new file mode 100644 index 00000000000..e8c5d0d404d --- /dev/null +++ b/app/graphql/mutations/concerns/mutations/resolves_subscription.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Mutations + module ResolvesSubscription + extend ActiveSupport::Concern + included do + argument :subscribed_state, + GraphQL::BOOLEAN_TYPE, + required: true, + description: 'The desired state of the subscription' + end + + def resolve(project_path:, iid:, subscribed_state:) + resource = authorized_find!(project_path: project_path, iid: iid) + project = resource.project + + resource.set_subscription(current_user, subscribed_state, project) + + { + resource.class.name.underscore.to_sym => resource, + errors: errors_on_object(resource) + } + end + end +end diff --git a/app/graphql/mutations/issues/set_subscription.rb b/app/graphql/mutations/issues/set_subscription.rb new file mode 100644 index 00000000000..a04c8f5ba2d --- /dev/null +++ b/app/graphql/mutations/issues/set_subscription.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class SetSubscription < Base + graphql_name 'IssueSetSubscription' + + include ResolvesSubscription + end + end +end diff --git a/app/graphql/mutations/merge_requests/set_subscription.rb b/app/graphql/mutations/merge_requests/set_subscription.rb index 1535481ab37..7d3c40185c9 100644 --- a/app/graphql/mutations/merge_requests/set_subscription.rb +++ b/app/graphql/mutations/merge_requests/set_subscription.rb @@ -5,22 +5,7 @@ module Mutations class SetSubscription < Base graphql_name 'MergeRequestSetSubscription' - argument :subscribed_state, - GraphQL::BOOLEAN_TYPE, - required: true, - description: 'The desired state of the subscription' - - def resolve(project_path:, iid:, subscribed_state:) - merge_request = authorized_find!(project_path: project_path, iid: iid) - project = merge_request.project - - merge_request.set_subscription(current_user, subscribed_state, project) - - { - merge_request: merge_request, - errors: errors_on_object(merge_request) - } - end + include ResolvesSubscription end end end diff --git a/app/graphql/types/issuable_state_enum.rb b/app/graphql/types/issuable_state_enum.rb index f2f6d6c6cab..543b7f8e5b2 100644 --- a/app/graphql/types/issuable_state_enum.rb +++ b/app/graphql/types/issuable_state_enum.rb @@ -8,5 +8,6 @@ module Types value 'opened' value 'closed' value 'locked' + value 'all' end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 49d51b626b2..8b4ea5d21d4 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -20,6 +20,7 @@ module Types mount_mutation Mutations::Issues::SetConfidential mount_mutation Mutations::Issues::SetLocked mount_mutation Mutations::Issues::SetDueDate + mount_mutation Mutations::Issues::SetSubscription mount_mutation Mutations::Issues::Update mount_mutation Mutations::MergeRequests::Create mount_mutation Mutations::MergeRequests::Update diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index dccb89eec79..62bdae39a37 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -205,7 +205,7 @@ module IssuablesHelper author_output end - output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip gl-ml-2', title: _('1st contribution!')) + output << content_tag(:span, (sprite_icon('first-contribution', size: 16, css_class: 'gl-icon gl-vertical-align-middle') if issuable.first_contribution?), class: 'has-tooltip gl-ml-2', title: _('1st contribution!')) output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block gl-ml-3") output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none") @@ -247,13 +247,6 @@ module IssuablesHelper html.html_safe end - def issuable_first_contribution_icon - content_tag(:span, class: 'fa-stack') do - concat(icon('certificate', class: "fa-stack-2x")) - concat(content_tag(:strong, '1', class: 'fa-inverse fa-stack-1x')) - end - end - def assigned_issuables_count(issuable_type) case issuable_type when :issues diff --git a/app/models/project.rb b/app/models/project.rb index a16669572df..b37dc3ed397 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1801,7 +1801,6 @@ class Project < ApplicationRecord return unless namespace mark_pages_as_not_deployed unless destroyed? - ::Projects::UpdatePagesConfigurationService.new(self).execute # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching diff --git a/app/services/metrics/dashboard/grafana_metric_embed_service.rb b/app/services/metrics/dashboard/grafana_metric_embed_service.rb index 8e72a185406..b8c5c17c738 100644 --- a/app/services/metrics/dashboard/grafana_metric_embed_service.rb +++ b/app/services/metrics/dashboard/grafana_metric_embed_service.rb @@ -33,7 +33,7 @@ module Metrics def from_cache(project_id, user_id, grafana_url) project = Project.find(project_id) - user = User.find(user_id) + user = User.find(user_id) if user_id.present? new(project, user, grafana_url: grafana_url) end @@ -56,7 +56,7 @@ module Metrics end def cache_key(*args) - [project.id, current_user.id, grafana_url] + [project.id, current_user&.id, grafana_url] end # Required for ReactiveCaching; Usage overridden by diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml index d725098752d..6cc4c8c1de7 100644 --- a/app/views/projects/notes/_actions.html.haml +++ b/app/views/projects/notes/_actions.html.haml @@ -1,7 +1,7 @@ - access = note_max_access_for_user(note) - if note.has_special_role?(Note::SpecialRole::FIRST_TIME_CONTRIBUTOR) %span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project.") } - = issuable_first_contribution_icon + = sprite_icon('first-contribution', size: 16, css_class: 'gl-icon gl-vertical-align-top') - if access.nonzero? %span.note-role.user-access-role= Gitlab::Access.human_access(access) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 14488aa7f59..d20519ff774 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -5,7 +5,7 @@ --- - :name: authorized_project_update:authorized_project_update_project_create :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -13,7 +13,7 @@ :tags: [] - :name: authorized_project_update:authorized_project_update_project_group_link_create :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -21,7 +21,7 @@ :tags: [] - :name: authorized_project_update:authorized_project_update_user_refresh_over_user_range :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -29,7 +29,7 @@ :tags: [] - :name: authorized_project_update:authorized_project_update_user_refresh_with_low_urgency :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -37,87 +37,87 @@ :tags: [] - :name: auto_devops:auto_devops_disable :feature_category: :auto_devops - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: auto_merge:auto_merge_process :feature_category: :continuous_delivery - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: chaos:chaos_cpu_spin :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: chaos:chaos_db_spin :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: chaos:chaos_kill :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: chaos:chaos_leak_mem :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: chaos:chaos_sleep :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: container_repository:cleanup_container_repository :feature_category: :container_registry - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: container_repository:delete_container_repository :feature_category: :container_registry - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:admin_email :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:authorized_project_update_periodic_recalculate :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -125,79 +125,79 @@ :tags: [] - :name: cronjob:ci_archive_traces_cron :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:container_expiration_policy :feature_category: :container_registry - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:environments_auto_stop_cron :feature_category: :continuous_delivery - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:expire_build_artifacts :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:gitlab_usage_ping :feature_category: :collection - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:import_export_project_cleanup :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:import_stuck_project_import_jobs :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:issue_due_scheduler :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:jira_import_stuck_jira_import_jobs :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:metrics_dashboard_schedule_annotations_prune :feature_category: :metrics - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -205,39 +205,39 @@ :tags: [] - :name: cronjob:namespaces_prune_aggregation_schedules :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:pages_domain_removal_cron :feature_category: :pages - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:pages_domain_ssl_renewal_cron :feature_category: :pages - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:pages_domain_verification_cron :feature_category: :pages - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:partition_creation :feature_category: :database - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -245,127 +245,127 @@ :tags: [] - :name: cronjob:personal_access_tokens_expiring :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:pipeline_schedule :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:prune_old_events :feature_category: :users - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:prune_web_hook_logs :feature_category: :integrations - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:remove_expired_group_links :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:remove_expired_members :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:remove_unreferenced_lfs_objects :feature_category: :git_lfs - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:repository_archive_cache :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:repository_check_dispatch :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:requests_profiles :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:schedule_migrate_external_diffs :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:stuck_ci_jobs :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:stuck_export_jobs :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:stuck_merge_jobs :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:trending_projects :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:update_container_registry_info :feature_category: :container_registry - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -373,11 +373,11 @@ :tags: [] - :name: cronjob:users_create_statistics :feature_category: :users - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: cronjob:x509_issuer_crl_check :feature_category: :source_code_management @@ -389,27 +389,27 @@ :tags: [] - :name: deployment:deployments_finished :feature_category: :continuous_delivery - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: deployment:deployments_forward_deployment :feature_category: :continuous_delivery - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: deployment:deployments_success :feature_category: :continuous_delivery - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_configure_istio :feature_category: :kubernetes_management @@ -417,7 +417,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_install_app :feature_category: :kubernetes_management @@ -425,7 +425,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_patch_app :feature_category: :kubernetes_management @@ -433,7 +433,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_provision :feature_category: :kubernetes_management @@ -441,15 +441,15 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_update_app :feature_category: :kubernetes_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_upgrade_app :feature_category: :kubernetes_management @@ -457,7 +457,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_wait_for_app_installation :feature_category: :kubernetes_management @@ -465,15 +465,15 @@ :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_wait_for_app_update :feature_category: :kubernetes_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:cluster_wait_for_ingress_ip_address :feature_category: :kubernetes_management @@ -481,23 +481,23 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_applications_activate_service :feature_category: :kubernetes_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_applications_deactivate_service :feature_category: :kubernetes_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_applications_uninstall :feature_category: :kubernetes_management @@ -505,7 +505,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_applications_wait_for_uninstall_app :feature_category: :kubernetes_management @@ -513,7 +513,7 @@ :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_cleanup_app :feature_category: :kubernetes_management @@ -521,7 +521,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_cleanup_project_namespace :feature_category: :kubernetes_management @@ -529,7 +529,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:clusters_cleanup_service_account :feature_category: :kubernetes_management @@ -537,7 +537,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gcp_cluster:wait_for_cluster_creation :feature_category: :kubernetes_management @@ -545,7 +545,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_import_diff_note :feature_category: :importers @@ -553,7 +553,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_import_issue :feature_category: :importers @@ -561,7 +561,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_import_lfs_object :feature_category: :importers @@ -569,7 +569,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_import_note :feature_category: :importers @@ -577,7 +577,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_import_pull_request :feature_category: :importers @@ -585,103 +585,103 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_refresh_import_jid :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_finish_import :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_import_base_data :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_import_issues_and_diff_notes :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_import_lfs_objects :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_import_notes :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_import_pull_requests :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_importer:github_import_stage_import_repository :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: hashed_storage:hashed_storage_migrator :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: hashed_storage:hashed_storage_project_migrate :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: hashed_storage:hashed_storage_project_rollback :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: hashed_storage:hashed_storage_rollbacker :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: incident_management:clusters_applications_check_prometheus_health :feature_category: :incident_management @@ -693,175 +693,175 @@ :tags: [] - :name: incident_management:incident_management_pager_duty_process_incident :feature_category: :incident_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: incident_management:incident_management_process_alert :feature_category: :incident_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: incident_management:incident_management_process_prometheus_alert :feature_category: :incident_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_advance_stage :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_import_issue :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_stage_finish_import :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_stage_import_attachments :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_stage_import_issues :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_stage_import_labels :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_stage_import_notes :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: jira_importer:jira_import_stage_start_import :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: mail_scheduler:mail_scheduler_issue_due :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: mail_scheduler:mail_scheduler_notification_service :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: object_pool:object_pool_create :feature_category: :gitaly - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: object_pool:object_pool_destroy :feature_category: :gitaly - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: object_pool:object_pool_join :feature_category: :gitaly - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: object_pool:object_pool_schedule_join :feature_category: :gitaly - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: object_storage:object_storage_background_move :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: object_storage:object_storage_migrate_uploads :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: package_repositories:packages_nuget_extraction :feature_category: :package_registry - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_background:archive_trace :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_background:ci_build_report_result :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -869,15 +869,15 @@ :tags: [] - :name: pipeline_background:ci_build_trace_chunk_flush :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_background:ci_daily_build_group_report_results :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -885,7 +885,7 @@ :tags: [] - :name: pipeline_background:ci_pipeline_success_unlock_artifacts :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -893,7 +893,7 @@ :tags: [] - :name: pipeline_background:ci_ref_delete_unlock_artifacts :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -901,7 +901,7 @@ :tags: [] - :name: pipeline_cache:expire_job_cache :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 3 @@ -909,7 +909,7 @@ :tags: [] - :name: pipeline_cache:expire_pipeline_cache :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 3 @@ -917,152 +917,152 @@ :tags: [] - :name: pipeline_creation:create_pipeline :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 4 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_creation:run_pipeline_schedule :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 4 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:build_coverage :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:build_trace_sections :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:ci_create_cross_project_pipeline :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:ci_pipeline_bridge_status :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:pipeline_metrics :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:pipeline_notification :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_default:pipeline_update_ci_ref_status :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_hooks:build_hooks :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_hooks:pipeline_hooks :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:build_finished :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 5 - :idempotent: + :idempotent: :tags: - :requires_disk_io - :name: pipeline_processing:build_queue :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:build_success :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:ci_build_prepare :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:ci_build_schedule :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:ci_resource_groups_assign_resource_from_resource_group :feature_category: :continuous_delivery - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:pipeline_process :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: pipeline_processing:pipeline_update :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 5 @@ -1070,7 +1070,7 @@ :tags: [] - :name: pipeline_processing:stage_update :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 5 @@ -1078,7 +1078,7 @@ :tags: [] - :name: pipeline_processing:update_head_pipeline_for_merge_request :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 5 @@ -1086,71 +1086,71 @@ :tags: [] - :name: repository_check:repository_check_batch :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: repository_check:repository_check_clear :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: repository_check:repository_check_single_repository :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: todos_destroyer:todos_destroyer_confidential_issue :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: todos_destroyer:todos_destroyer_entity_leave :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: todos_destroyer:todos_destroyer_group_private :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: todos_destroyer:todos_destroyer_private_features :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: todos_destroyer:todos_destroyer_project_private :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: unassign_issuables:members_destroyer_unassign_issuables :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -1158,7 +1158,7 @@ :tags: [] - :name: update_namespace_statistics:namespaces_root_statistics :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -1166,7 +1166,7 @@ :tags: [] - :name: update_namespace_statistics:namespaces_schedule_aggregation :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -1174,7 +1174,7 @@ :tags: [] - :name: authorized_keys :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 2 @@ -1182,7 +1182,7 @@ :tags: [] - :name: authorized_projects :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 2 @@ -1190,11 +1190,11 @@ :tags: [] - :name: background_migration :feature_category: :database - :has_external_dependencies: + :has_external_dependencies: :urgency: :throttled :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: chat_notification :feature_category: :chatops @@ -1202,11 +1202,11 @@ :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: create_commit_signature :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 @@ -1214,91 +1214,91 @@ :tags: [] - :name: create_evidence :feature_category: :release_evidence - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: create_note_diff_file :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: default - :feature_category: - :has_external_dependencies: - :urgency: - :resource_boundary: + :feature_category: + :has_external_dependencies: + :urgency: + :resource_boundary: :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: delete_diff_files :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: delete_merged_branches :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: delete_stored_files :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: delete_user :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: design_management_new_version :feature_category: :design_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :memory :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: detect_repository_languages :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: email_receiver :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: emails_on_push :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: error_tracking_issue_link :feature_category: :error_tracking @@ -1306,23 +1306,23 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: expire_build_instance_artifacts :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: export_csv :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: external_service_reactive_caching :feature_category: :not_owned @@ -1330,15 +1330,15 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: file_hook :feature_category: :integrations - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: flush_counter_increments :feature_category: :not_owned @@ -1350,87 +1350,87 @@ :tags: [] - :name: git_garbage_collect :feature_category: :gitaly - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: github_import_advance_stage :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: gitlab_shell :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: group_destroy :feature_category: :subgroups - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: group_export :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: group_import :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: import_issues_csv :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: invalid_gpg_signature_update :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: irker :feature_category: :integrations - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: mailers - :feature_category: - :has_external_dependencies: - :urgency: - :resource_boundary: + :feature_category: + :has_external_dependencies: + :urgency: + :resource_boundary: :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: merge :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 5 @@ -1438,7 +1438,7 @@ :tags: [] - :name: merge_request_mergeability_check :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -1446,7 +1446,7 @@ :tags: [] - :name: metrics_dashboard_prune_old_annotations :feature_category: :metrics - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 @@ -1454,87 +1454,87 @@ :tags: [] - :name: migrate_external_diffs :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: namespaceless_project_destroy :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: new_issue :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: new_merge_request :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: new_note :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: pages :feature_category: :pages - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: pages_domain_ssl_renewal :feature_category: :pages - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: pages_domain_verification :feature_category: :pages - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: phabricator_import_import_tasks :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: post_receive :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 5 - :idempotent: + :idempotent: :tags: [] - :name: process_commit :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 3 @@ -1542,35 +1542,35 @@ :tags: [] - :name: project_cache :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: project_daily_statistics :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: project_destroy :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: project_export :feature_category: :importers - :has_external_dependencies: + :has_external_dependencies: :urgency: :throttled :resource_boundary: :memory :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: project_service :feature_category: :integrations @@ -1578,11 +1578,11 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: project_update_repository_storage :feature_category: :gitaly - :has_external_dependencies: + :has_external_dependencies: :urgency: :throttled :resource_boundary: :unknown :weight: 1 @@ -1590,7 +1590,7 @@ :tags: [] - :name: prometheus_create_default_alerts :feature_category: :incident_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 1 @@ -1598,59 +1598,59 @@ :tags: [] - :name: propagate_integration :feature_category: :integrations - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 :idempotent: true :tags: [] - :name: propagate_service_template - :feature_category: :source_code_management - :has_external_dependencies: + :feature_category: :integrations + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: reactive_caching :feature_category: :not_owned - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :cpu :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: rebase :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: remote_mirror_notification :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: repository_cleanup :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: repository_fork :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: repository_import :feature_category: :importers @@ -1658,15 +1658,15 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: repository_remove_remote :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: repository_update_remote_mirror :feature_category: :source_code_management @@ -1674,51 +1674,51 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: self_monitoring_project_create :feature_category: :metrics - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: self_monitoring_project_delete :feature_category: :metrics - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 2 - :idempotent: + :idempotent: :tags: [] - :name: service_desk_email_receiver :feature_category: :issue_tracking - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: system_hook_push :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: update_external_pull_requests :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: update_highest_role :feature_category: :authentication_and_authorization - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :unknown :weight: 2 @@ -1726,27 +1726,27 @@ :tags: [] - :name: update_merge_requests :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :high :resource_boundary: :cpu :weight: 3 - :idempotent: + :idempotent: :tags: [] - :name: update_project_statistics :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: upload_checksum :feature_category: :geo_replication - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: web_hook :feature_category: :integrations @@ -1754,11 +1754,11 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: + :idempotent: :tags: [] - :name: x509_certificate_revoke :feature_category: :source_code_management - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index f2222c7be5e..459fc5992d8 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -33,7 +33,10 @@ class GitGarbageCollectWorker # rubocop:disable Scalability/IdempotentWorker # Refresh the branch cache in case garbage collection caused a ref lookup to fail flush_ref_caches(project) if task == :gc - project.repository.expire_statistics_caches if task != :pack_refs + if task != :pack_refs + project.repository.expire_statistics_caches + Projects::UpdateStatisticsService.new(project, nil, statistics: [:repository_size, :lfs_objects_size]).execute + end # In case pack files are deleted, release libgit2 cache and open file # descriptors ASAP instead of waiting for Ruby garbage collection diff --git a/app/workers/propagate_service_template_worker.rb b/app/workers/propagate_service_template_worker.rb index f3a6bda1821..37d5ccb656d 100644 --- a/app/workers/propagate_service_template_worker.rb +++ b/app/workers/propagate_service_template_worker.rb @@ -4,7 +4,7 @@ class PropagateServiceTemplateWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker - feature_category :source_code_management + feature_category :integrations LEASE_TIMEOUT = 4.hours.to_i diff --git a/changelogs/unreleased/217014-grafanametricembedservice-undefined-method-id-for-nil-nilclass.yml b/changelogs/unreleased/217014-grafanametricembedservice-undefined-method-id-for-nil-nilclass.yml new file mode 100644 index 00000000000..577e98c1a4c --- /dev/null +++ b/changelogs/unreleased/217014-grafanametricembedservice-undefined-method-id-for-nil-nilclass.yml @@ -0,0 +1,5 @@ +--- +title: Allow anonymous users to view embedded Grafana metrics in public project +merge_request: 37844 +author: +type: fixed diff --git a/changelogs/unreleased/225921-replace-fa-certificate-icons-with-gitlab-svg-first-contribution-ic.yml b/changelogs/unreleased/225921-replace-fa-certificate-icons-with-gitlab-svg-first-contribution-ic.yml new file mode 100644 index 00000000000..2fa362a43c9 --- /dev/null +++ b/changelogs/unreleased/225921-replace-fa-certificate-icons-with-gitlab-svg-first-contribution-ic.yml @@ -0,0 +1,5 @@ +--- +title: Replace fa-certificate icon with first-contribution svg +merge_request: 38154 +author: +type: changed diff --git a/changelogs/unreleased/226927-immediately-update-repository-statistics-when-running-housekeeping.yml b/changelogs/unreleased/226927-immediately-update-repository-statistics-when-running-housekeeping.yml new file mode 100644 index 00000000000..fb5cf05e12b --- /dev/null +++ b/changelogs/unreleased/226927-immediately-update-repository-statistics-when-running-housekeeping.yml @@ -0,0 +1,6 @@ +--- +title: Immediately update project statistics when running housekeeping or repository + cleanup +merge_request: 37579 +author: +type: other diff --git a/changelogs/unreleased/229400-incident-state.yml b/changelogs/unreleased/229400-incident-state.yml new file mode 100644 index 00000000000..3b3ba3f0a60 --- /dev/null +++ b/changelogs/unreleased/229400-incident-state.yml @@ -0,0 +1,5 @@ +--- +title: Add incident state columns +merge_request: 37889 +author: +type: other diff --git a/changelogs/unreleased/graphql-issue-subscribe.yml b/changelogs/unreleased/graphql-issue-subscribe.yml new file mode 100644 index 00000000000..032cdbed38b --- /dev/null +++ b/changelogs/unreleased/graphql-issue-subscribe.yml @@ -0,0 +1,5 @@ +--- +title: Allows setting of issue subscribe status in GraphQL API. +merge_request: 38051 +author: +type: added diff --git a/changelogs/unreleased/mo-add-index-to-ci-pipeline.yml b/changelogs/unreleased/mo-add-index-to-ci-pipeline.yml new file mode 100644 index 00000000000..12107e1a4b9 --- /dev/null +++ b/changelogs/unreleased/mo-add-index-to-ci-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 for pipeline charts page +merge_request: 38226 +author: +type: fixed diff --git a/changelogs/unreleased/ph-enableAutoExpandByDefault.yml b/changelogs/unreleased/ph-enableAutoExpandByDefault.yml new file mode 100644 index 00000000000..c1706a088fb --- /dev/null +++ b/changelogs/unreleased/ph-enableAutoExpandByDefault.yml @@ -0,0 +1,5 @@ +--- +title: Auto expand collapsed diffs when viewing diffs file-by-file +merge_request: 38296 +author: +type: added diff --git a/config/initializers/sidekiq_cluster.rb b/config/initializers/sidekiq_cluster.rb index 4ff8dd9b936..2f9c1de47eb 100644 --- a/config/initializers/sidekiq_cluster.rb +++ b/config/initializers/sidekiq_cluster.rb @@ -14,10 +14,10 @@ if ENV['ENABLE_SIDEKIQ_CLUSTER'] if Process.ppid != parent Process.kill(:TERM, Process.pid) - # Wait for just a few extra seconds for a final attempt to - # gracefully terminate. Considering the parent (cluster) process - # have changed (SIGKILL'd), it shouldn't take long to shutdown. - sleep(5) + # Allow sidekiq to cleanly terminate and push any running jobs back + # into the queue. We use the configured timeout and add a small + # grace period + sleep(Sidekiq.options[:timeout] + 5) # Signaling the Sidekiq Pgroup as KILL is not forwarded to # a possible child process. In Sidekiq Cluster, all child Sidekiq diff --git a/db/migrate/20200728080250_replace_unique_index_on_cycle_analytics_stages.rb b/db/migrate/20200728080250_replace_unique_index_on_cycle_analytics_stages.rb new file mode 100644 index 00000000000..0e562ae27e2 --- /dev/null +++ b/db/migrate/20200728080250_replace_unique_index_on_cycle_analytics_stages.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class ReplaceUniqueIndexOnCycleAnalyticsStages < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + OLD_INDEX_NAME = 'index_analytics_ca_group_stages_on_group_id_and_name' + NEW_INDEX_NAME = 'index_group_stages_on_group_id_group_value_stream_id_and_name' + + disable_ddl_transaction! + + def up + add_concurrent_index(:analytics_cycle_analytics_group_stages, + [:group_id, :group_value_stream_id, :name], + unique: true, + name: NEW_INDEX_NAME) + + remove_concurrent_index_by_name :analytics_cycle_analytics_group_stages, OLD_INDEX_NAME + end + + def down + # Removing duplicated records (group_id, name) that would prevent re-creating the old index. + execute <<-SQL + DELETE FROM analytics_cycle_analytics_group_stages + USING ( + SELECT group_id, name, MIN(id) as min_id + FROM analytics_cycle_analytics_group_stages + GROUP BY group_id, name + HAVING COUNT(id) > 1 + ) as analytics_cycle_analytics_group_stages_name_duplicates + WHERE analytics_cycle_analytics_group_stages_name_duplicates.group_id = analytics_cycle_analytics_group_stages.group_id + AND analytics_cycle_analytics_group_stages_name_duplicates.name = analytics_cycle_analytics_group_stages.name + AND analytics_cycle_analytics_group_stages_name_duplicates.min_id <> analytics_cycle_analytics_group_stages.id + SQL + + add_concurrent_index(:analytics_cycle_analytics_group_stages, + [:group_id, :name], + unique: true, + name: OLD_INDEX_NAME) + + remove_concurrent_index_by_name :analytics_cycle_analytics_group_stages, NEW_INDEX_NAME + end +end diff --git a/db/migrate/20200729202222_add_index_to_ci_pipeline_project_id_created_at.rb b/db/migrate/20200729202222_add_index_to_ci_pipeline_project_id_created_at.rb new file mode 100644 index 00000000000..d1ee9c49d30 --- /dev/null +++ b/db/migrate/20200729202222_add_index_to_ci_pipeline_project_id_created_at.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexToCiPipelineProjectIdCreatedAt < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :ci_pipelines, [:project_id, :created_at] + end + + def down + remove_concurrent_index :ci_pipelines, [:project_id, :created_at] + end +end diff --git a/db/schema_migrations/20200728080250 b/db/schema_migrations/20200728080250 new file mode 100644 index 00000000000..137290c0e86 --- /dev/null +++ b/db/schema_migrations/20200728080250 @@ -0,0 +1 @@ +546555a009e8923ea8b976ce38d882d387407fb03e7bbcb9c760df53bafd1f91
\ No newline at end of file diff --git a/db/schema_migrations/20200729202222 b/db/schema_migrations/20200729202222 new file mode 100644 index 00000000000..2ab73a764f3 --- /dev/null +++ b/db/schema_migrations/20200729202222 @@ -0,0 +1 @@ +2976f459ac9cd0780e90077ebe4ce5ca8dc41e62b4dab1f96e39738624ad9d04
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 4b1f18073c1..770fac47ffc 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18880,8 +18880,6 @@ CREATE INDEX index_analytics_ca_group_stages_on_end_event_label_id ON public.ana CREATE INDEX index_analytics_ca_group_stages_on_group_id ON public.analytics_cycle_analytics_group_stages USING btree (group_id); -CREATE UNIQUE INDEX index_analytics_ca_group_stages_on_group_id_and_name ON public.analytics_cycle_analytics_group_stages USING btree (group_id, name); - CREATE INDEX index_analytics_ca_group_stages_on_relative_position ON public.analytics_cycle_analytics_group_stages USING btree (relative_position); CREATE INDEX index_analytics_ca_group_stages_on_start_event_label_id ON public.analytics_cycle_analytics_group_stages USING btree (start_event_label_id); @@ -19150,6 +19148,8 @@ CREATE INDEX index_ci_pipelines_on_merge_request_id ON public.ci_pipelines USING CREATE INDEX index_ci_pipelines_on_pipeline_schedule_id ON public.ci_pipelines USING btree (pipeline_schedule_id); +CREATE INDEX index_ci_pipelines_on_project_id_and_created_at ON public.ci_pipelines USING btree (project_id, created_at); + CREATE INDEX index_ci_pipelines_on_project_id_and_id_desc ON public.ci_pipelines USING btree (project_id, id DESC); CREATE UNIQUE INDEX index_ci_pipelines_on_project_id_and_iid ON public.ci_pipelines USING btree (project_id, iid) WHERE (iid IS NOT NULL); @@ -19654,6 +19654,8 @@ CREATE INDEX index_group_group_links_on_shared_with_group_id ON public.group_gro CREATE INDEX index_group_import_states_on_group_id ON public.group_import_states USING btree (group_id); +CREATE UNIQUE INDEX index_group_stages_on_group_id_group_value_stream_id_and_name ON public.analytics_cycle_analytics_group_stages USING btree (group_id, group_value_stream_id, name); + CREATE UNIQUE INDEX index_group_wiki_repositories_on_disk_path ON public.group_wiki_repositories USING btree (disk_path); CREATE INDEX index_group_wiki_repositories_on_shard_id ON public.group_wiki_repositories USING btree (shard_id); diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 4343aa10d96..39b6c44811b 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -5861,6 +5861,7 @@ type InstanceSecurityDashboard { State of a GitLab issue or merge request """ enum IssuableState { + all closed locked opened @@ -6434,6 +6435,51 @@ type IssueSetLockedPayload { } """ +Autogenerated input type of IssueSetSubscription +""" +input IssueSetSubscriptionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IID of the issue to mutate + """ + iid: String! + + """ + The project the issue to mutate is in + """ + projectPath: ID! + + """ + The desired state of the subscription + """ + subscribedState: Boolean! +} + +""" +Autogenerated return type of IssueSetSubscription +""" +type IssueSetSubscriptionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Errors encountered during execution of the mutation. + """ + errors: [String!]! + + """ + The issue after mutation + """ + issue: Issue +} + +""" Autogenerated input type of IssueSetWeight """ input IssueSetWeightInput { @@ -6562,6 +6608,7 @@ enum IssueSort { State of a GitLab issue """ enum IssueState { + all closed locked opened @@ -7987,6 +8034,7 @@ type MergeRequestSetWipPayload { State of a GitLab merge request """ enum MergeRequestState { + all closed locked merged @@ -8351,6 +8399,7 @@ type Mutation { issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload issueSetLocked(input: IssueSetLockedInput!): IssueSetLockedPayload + issueSetSubscription(input: IssueSetSubscriptionInput!): IssueSetSubscriptionPayload issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload jiraImportUsers(input: JiraImportUsersInput!): JiraImportUsersPayload diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index fcc73efd770..f0d66d48e72 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -16186,6 +16186,12 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "all", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -17854,6 +17860,136 @@ }, { "kind": "INPUT_OBJECT", + "name": "IssueSetSubscriptionInput", + "description": "Autogenerated input type of IssueSetSubscription", + "fields": null, + "inputFields": [ + { + "name": "projectPath", + "description": "The project the issue to mutate is in", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "iid", + "description": "The IID of the issue to mutate", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "subscribedState", + "description": "The desired state of the subscription", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "IssueSetSubscriptionPayload", + "description": "Autogenerated return type of IssueSetSubscription", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "errors", + "description": "Errors encountered during execution of the mutation.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issue", + "description": "The issue after mutation", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Issue", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", "name": "IssueSetWeightInput", "description": "Autogenerated input type of IssueSetWeight", "fields": null, @@ -18108,6 +18244,12 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "all", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -22395,6 +22537,12 @@ "deprecationReason": null }, { + "name": "all", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "merged", "description": null, "isDeprecated": false, @@ -24358,6 +24506,33 @@ "deprecationReason": null }, { + "name": "issueSetSubscription", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "IssueSetSubscriptionInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "IssueSetSubscriptionPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "issueSetWeight", "description": null, "args": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5eaff5350eb..07bee288217 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -985,6 +985,16 @@ Autogenerated return type of IssueSetLocked | `errors` | String! => Array | Errors encountered during execution of the mutation. | | `issue` | Issue | The issue after mutation | +## IssueSetSubscriptionPayload + +Autogenerated return type of IssueSetSubscription + +| Name | Type | Description | +| --- | ---- | ---------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `errors` | String! => Array | Errors encountered during execution of the mutation. | +| `issue` | Issue | The issue after mutation | + ## IssueSetWeightPayload Autogenerated return type of IssueSetWeight diff --git a/doc/development/code_review.md b/doc/development/code_review.md index fd53ce79534..5753b96c774 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -230,7 +230,7 @@ Instead these should be sent to the [Release Manager](https://about.gitlab.com/c - Ask for clarification. ("I didn't understand. Can you clarify?") - Avoid selective ownership of code. ("mine", "not mine", "yours") - Avoid using terms that could be seen as referring to personal traits. ("dumb", - "stupid"). Assume everyone is attractive, intelligent, and well-meaning. + "stupid"). Assume everyone is intelligent and well-meaning. - Be explicit. Remember people don't always understand your intentions online. - Be humble. ("I'm not sure - let's look it up.") - Don't use hyperbole. ("always", "never", "endlessly", "nothing") diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 71d8a8fc7f9..cc492cc2d1a 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -481,7 +481,7 @@ We treat documentation as code, and so use tests in our CI pipeline to maintain standards and quality of the docs. The current tests, which run in CI jobs when a merge request with new or changed docs is submitted, are: -- [`docs lint`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/docs.gitlab-ci.yml#L48): +- [`docs lint`](https://gitlab.com/gitlab-org/gitlab/-/blob/0b562014f7b71f98540e682c8d662275f0011f2f/.gitlab/ci/docs.gitlab-ci.yml#L41): Runs several tests on the content of the docs themselves: - [`lint-doc.sh` script](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/lint-doc.sh) runs the following checks and linters: @@ -492,33 +492,20 @@ merge request with new or changed docs is submitted, are: - [markdownlint](#markdownlint). - [Vale](#vale). - Nanoc tests: - - [`internal_links`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/docs.gitlab-ci.yml#L67) + - [`internal_links`](https://gitlab.com/gitlab-org/gitlab/-/blob/0b562014f7b71f98540e682c8d662275f0011f2f/.gitlab/ci/docs.gitlab-ci.yml#L58) checks that all internal links (ex: `[link](../index.md)`) are valid. - - [`internal_anchors`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/docs.gitlab-ci.yml#L69) + - [`internal_anchors`](https://gitlab.com/gitlab-org/gitlab/-/blob/0b562014f7b71f98540e682c8d662275f0011f2f/.gitlab/ci/docs.gitlab-ci.yml#L60) checks that all internal anchors (ex: `[link](../index.md#internal_anchor)`) are valid. + - [`ui-docs-links lint`](https://gitlab.com/gitlab-org/gitlab/-/blob/0b562014f7b71f98540e682c8d662275f0011f2f/.gitlab/ci/docs.gitlab-ci.yml#L62) + checks that all links to docs from UI elements (`app/views` files, for example) + are linking to valid docs and anchors. -### Running tests +### Run tests locally Apart from [previewing your changes locally](#previewing-the-changes-live), you can also run all lint checks and Nanoc tests locally. -#### Nanoc tests - -To execute Nanoc tests locally: - -1. Navigate to the [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory. -1. Run: - - ```shell - # Check for broken internal links - bundle exec nanoc check internal_links - - # Check for broken external links (might take a lot of time to complete). - # This test is set to be allowed to fail and is run only in the gitlab-docs project CI - bundle exec nanoc check internal_anchors - ``` - #### Lint checks Lint checks are performed by the [`lint-doc.sh`](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/lint-doc.sh) @@ -550,6 +537,57 @@ The output should be similar to: Note that this requires you to either have the required lint tools installed on your machine, or a working Docker installation, in which case an image with these tools pre-installed will be used. +#### Nanoc tests + +To execute Nanoc tests locally: + +1. Navigate to the [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory. +1. Run: + + ```shell + # Check for broken internal links + bundle exec nanoc check internal_links + + # Check for broken external links (might take a lot of time to complete). + # This test is set to be allowed to fail and is run only in the gitlab-docs project CI + bundle exec nanoc check internal_anchors + ``` + +#### `ui-docs-links` test + +The `ui-docs-links lint` job uses `haml-lint` to test that all links to docs from +UI elements (`app/views` files, for example) are linking to valid docs and anchors. + +To run the `ui-docs-links` test locally: + +1. Open the `gitlab` directory in a terminal window. +1. Run: + + ```shell + bundle exec haml-lint -i DocumentationLinks + ``` + +If you receive an error the first time you run this test, run `bundle install`, which +installs GitLab's dependencies, and try again. + +If you don't want to install all of GitLab's dependencies to test the links, you can: + +1. Open the `gitlab` directory in a terminal window. +1. Install `haml-lint`: + + ```shell + gem install haml_lint + ``` + +1. Run: + + ```shell + haml-lint -i DocumentationLinks + ``` + +If you manually install `haml-lint` with this process, it will not update automatically +and you should make sure your version matches the version used by GitLab. + ### Local linters To help adhere to the [documentation style guidelines](styleguide.md), and improve the content diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index 3f6ae556c23..3a0b3b0a128 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -85,6 +85,7 @@ Default client accepts two parameters: `resolvers` and `config`. - `cacheConfig` field accepts an optional object of settings to [customize Apollo cache](https://www.apollographql.com/docs/react/caching/cache-configuration/#configuring-the-cache) - `baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (i.e.`${gon.relative_url_root}/api/graphql`) - `assumeImmutableResults` (set to `false` by default) - this setting, when set to `true`, will assume that every single operation on updating Apollo Cache is immutable. It also sets `freezeResults` to `true`, so any attempt on mutating Apollo Cache will throw a console warning in development environment. Please ensure you're following the immutability pattern on cache update operations before setting this option to `true`. + - `fetchPolicy` determines how you want your component to interact with the Apollo cache. Defaults to "cache-first". ## GraphQL Queries @@ -167,9 +168,7 @@ import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; Vue.use(VueApollo); -const defaultClient = createDefaultClient({ - resolvers: {} -}); +const defaultClient = createDefaultClient(); defaultClient.cache.writeData({ data: { @@ -257,10 +256,7 @@ We need to pass resolvers object to our existing Apollo Client: import createDefaultClient from '~/lib/graphql'; import resolvers from './graphql/resolvers'; -const defaultClient = createDefaultClient( - {}, - resolvers, -); +const defaultClient = createDefaultClient(resolvers); ``` Now every single time on attempt to fetch a version, our client will fetch `id` and `sha` from the remote API endpoint and will assign our hardcoded values to `author` and `createdAt` version properties. With this data, frontend developers are able to work on UI part without being blocked by backend. When actual response is added to the API, a custom local resolver can be removed fast and the only change to query/fragment is `@client` directive removal. diff --git a/doc/development/sql.md b/doc/development/sql.md index d584a26e455..3b969c7d27a 100644 --- a/doc/development/sql.md +++ b/doc/development/sql.md @@ -218,9 +218,9 @@ Project.select(:id, :user_id).joins(:merge_requests) ## Plucking IDs -This can't be stressed enough: **never** use ActiveRecord's `pluck` to pluck a -set of values into memory only to use them as an argument for another query. For -example, this will make the database **very** sad: +Never use ActiveRecord's `pluck` to pluck a set of values into memory only to +use them as an argument for another query. For example, this will execute an +extra unecessary database query and load a lot of unecessary data into memory: ```ruby projects = Project.all.pluck(:id) diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index ec2806cb439..12ba55cafdc 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -31,8 +31,6 @@ file path fragments to start seeing results. ## Syntax highlighting -> Support for `.gitlab-ci.yml` validation [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218472) in GitLab 13.2. - As expected from an IDE, syntax highlighting for many languages within the Web IDE will make your direct editing even easier. @@ -44,14 +42,6 @@ The Web IDE currently provides: - IntelliSense and validation support (displaying errors and warnings, providing smart completions, formatting, and outlining) for some languages. For example: TypeScript, JavaScript, CSS, LESS, SCSS, JSON, and HTML. -- Validation support for certain JSON and YAML files using schemas based on the - [JSON Schema Store](https://www.schemastore.org/json/). This feature - is only supported for the `.gitlab-ci.yml` file. - - NOTE: **Note:** - Validation support based on schemas is hidden behind - the feature flag `:schema_linting` on self-managed installations. To enable the - feature, you can [turn on the feature flag in Rails console](../../../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags). Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/), you can find a more complete list of supported languages in the @@ -63,6 +53,37 @@ If you are missing Syntax Highlighting support for any language, we prepared a s NOTE: **Note:** Single file editing is based on the [Ace Editor](https://ace.c9.io). +### Schema based validation + +> - Support for `.gitlab-ci.yml` validation [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218472) in GitLab 13.2. +> - It was deployed behind a feature flag, disabled by default. +> - It's enabled on GitLab.com. +> - It cannot be enabled or disabled per-project. +> - For GitLab self-managed instances, GitLab administrators can opt to [enable it](#enable-or-disable-schema-based-validation-core-only). + +The Web IDE provides validation support for certain JSON and YAML files using schemas +based on the [JSON Schema Store](https://www.schemastore.org/json/). This feature is +only supported for the `.gitlab-ci.yml` file. + +#### Enable or disable Schema based validation **(CORE ONLY)** + +Schema based validation is under development and not ready for production use. It is +deployed behind a feature flag that is **disabled by default** for self-managed instances, +[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) +can enable it for your instance. + +To enable it: + +```ruby +Feature.enable(:schema_linting) +``` + +To disable it: + +```ruby +Feature.disable(:schema_linting) +``` + ### Themes > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in GitLab in 13.0. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 689e96a765b..5787c6f46e6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8653,6 +8653,9 @@ msgstr "" msgid "Edit Release" msgstr "" +msgid "Edit Requirement" +msgstr "" + msgid "Edit Slack integration" msgstr "" @@ -12713,9 +12716,15 @@ msgstr "" msgid "Incident Management Limits" msgstr "" +msgid "IncidentManagement|All incidents" +msgstr "" + msgid "IncidentManagement|Assignees" msgstr "" +msgid "IncidentManagement|Closed" +msgstr "" + msgid "IncidentManagement|Create incident" msgstr "" @@ -12731,6 +12740,9 @@ msgstr "" msgid "IncidentManagement|No incidents to display." msgstr "" +msgid "IncidentManagement|Open" +msgstr "" + msgid "IncidentManagement|There was an error displaying the incidents." msgstr "" @@ -15803,6 +15815,9 @@ msgstr "" msgid "New Project" msgstr "" +msgid "New Requirement" +msgstr "" + msgid "New Snippet" msgstr "" @@ -20335,9 +20350,6 @@ msgstr "" msgid "Required in this project." msgstr "" -msgid "Requirement" -msgstr "" - msgid "Requirement %{reference} has been added" msgstr "" diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index d9d3f566bce..52f6c87b6aa 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -217,7 +217,7 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do it_behaves_like 'all pipelines' - it 'includes custom filters' do + it 'includes custom filters', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/233077' do aggregate_failures 'UploadLinkFilter' do expect(doc).to parse_upload_links end @@ -282,7 +282,7 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do it_behaves_like 'all pipelines' - it 'includes custom filters' do + it 'includes custom filters', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/233077' do aggregate_failures 'UploadLinkFilter' do expect(doc).to parse_upload_links end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 73d033cbdb8..3db870f229a 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -2,9 +2,28 @@ require 'spec_helper' -RSpec.shared_examples_for 'snippet editor' do +RSpec.describe 'Projects > Snippets > Create Snippet', :js do + include DropzoneHelper + + let_it_be(:user) { create(:user) } + let_it_be(:project) do + create(:project, :public, creator: user).tap do |p| + p.add_maintainer(user) + end + end + + let(:title) { 'My Snippet Title' } + let(:file_content) { 'Hello World!' } + let(:md_description) { 'My Snippet **Description**' } + let(:description) { 'My Snippet Description' } + before do + stub_feature_flags(snippets_vue: false) stub_feature_flags(snippets_edit_vue: false) + + sign_in(user) + + visit new_project_snippet_path(project) end def description_field @@ -12,137 +31,81 @@ RSpec.shared_examples_for 'snippet editor' do end def fill_form - fill_in 'project_snippet_title', with: 'My Snippet Title' + fill_in 'project_snippet_title', with: title # Click placeholder first to expand full description field description_field.click - fill_in 'project_snippet_description', with: 'My Snippet **Description**' + fill_in 'project_snippet_description', with: md_description page.within('.file-editor') do el = find('.inputarea') - el.send_keys 'Hello World!' + el.send_keys file_content end end - context 'when a user is authenticated' do - before do - stub_feature_flags(snippets_vue: false) - project.add_maintainer(user) - sign_in(user) + it 'shows collapsible description input' do + collapsed = description_field - visit project_snippets_path(project) - - # Wait for the SVG to ensure the button location doesn't shift - within('.empty-state') { find('img.js-lazy-loaded') } - click_on('New snippet') - wait_for_requests - end + expect(page).not_to have_field('project_snippet_description') + expect(collapsed).to be_visible - it 'shows collapsible description input' do - collapsed = description_field + collapsed.click - expect(page).not_to have_field('project_snippet_description') - expect(collapsed).to be_visible + expect(page).to have_field('project_snippet_description') + expect(collapsed).not_to be_visible + end - collapsed.click + it 'creates a new snippet' do + fill_form + click_button('Create snippet') + wait_for_requests - expect(page).to have_field('project_snippet_description') - expect(collapsed).not_to be_visible + expect(page).to have_content(title) + expect(page).to have_content(file_content) + page.within('.snippet-header .description') do + expect(page).to have_content(description) + expect(page).to have_selector('strong') end + end - it 'creates a new snippet' do - fill_form - click_button('Create snippet') - wait_for_requests + it 'uploads a file when dragging into textarea' do + fill_form + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - expect(page).to have_content('My Snippet Title') - expect(page).to have_content('Hello World!') - page.within('.snippet-header .description') do - expect(page).to have_content('My Snippet Description') - expect(page).to have_selector('strong') - end - end + expect(page.find_field('project_snippet_description').value).to have_content('banana_sample') - it 'uploads a file when dragging into textarea' do - fill_form - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + click_button('Create snippet') + wait_for_requests - expect(page.find_field("project_snippet_description").value).to have_content('banana_sample') + link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] + expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) + end - click_button('Create snippet') - wait_for_requests + it 'displays validation errors' do + fill_in 'project_snippet_title', with: title + click_button('Create snippet') + wait_for_requests - link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) - end + expect(page).to have_selector('#error_explanation') + end - it 'creates a snippet when all required fields are filled in after validation failing' do - fill_in 'project_snippet_title', with: 'My Snippet Title' - click_button('Create snippet') + context 'when the git operation fails' do + let(:error) { 'Error creating the snippet' } - expect(page).to have_selector('#error_explanation') + before do + allow_next_instance_of(Snippets::CreateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, error) + end fill_form - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - find("input[value='Create snippet']").send_keys(:return) + click_button('Create snippet') wait_for_requests - - expect(page).to have_content('My Snippet Title') - expect(page).to have_content('Hello World!') - page.within('.snippet-header .description') do - expect(page).to have_content('My Snippet Description') - expect(page).to have_selector('strong') - end - link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) - end - - context 'when the git operation fails' do - let(:error) { 'Error creating the snippet' } - - before do - allow_next_instance_of(Snippets::CreateService) do |instance| - allow(instance).to receive(:create_commit).and_raise(StandardError, error) - end - - fill_form - - click_button('Create snippet') - wait_for_requests - end - - it 'displays the error' do - expect(page).to have_content(error) - end - - it 'renders new page' do - expect(page).to have_content('New Snippet') - end end - end - - context 'when a user is not authenticated' do - before do - stub_feature_flags(snippets_vue: false) - end - - it 'shows a public snippet on the index page but not the New snippet button' do - snippet = create(:project_snippet, :public, :repository, project: project) - - visit project_snippets_path(project) - expect(page).to have_content(snippet.title) - expect(page).not_to have_content('New snippet') + it 'renders the new page and displays the error' do + expect(page).to have_content(error) + expect(page).to have_content('New Snippet') end end end - -RSpec.describe 'Projects > Snippets > Create Snippet', :js do - include DropzoneHelper - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } - - it_behaves_like "snippet editor" -end diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb index 0f6429d49f6..8fded3cde80 100644 --- a/spec/features/projects/snippets/show_spec.rb +++ b/spec/features/projects/snippets/show_spec.rb @@ -3,157 +3,41 @@ require 'spec_helper' RSpec.describe 'Projects > Snippets > Project snippet', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) } + let_it_be(:user) { create(:user) } + let_it_be(:project) do + create(:project, creator: user).tap do |p| + p.add_maintainer(user) + end + end + + let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) } before do stub_feature_flags(snippets_vue: false) - project.add_maintainer(user) + sign_in(user) end - context 'Ruby file' do - let(:file_name) { 'popen.rb' } - let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data } + it_behaves_like 'show and render proper snippet blob' do + let(:anchor) { nil } - before do - visit project_snippet_path(project, snippet) + subject do + visit project_snippet_path(project, snippet, anchor: anchor) wait_for_requests end - - it 'displays the blob' do - aggregate_failures do - # shows highlighted Ruby code - expect(page).to have_content("require 'fileutils'") - - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - - # shows a raw button - expect(page).to have_link('Open raw') - - # shows a download button - expect(page).to have_link('Download') - end - end end - context 'Markdown file' do - let(:file_name) { 'ruby-style-guide.md' } - let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data } - - context 'visiting directly' do - before do - visit project_snippet_path(project, snippet) - - wait_for_requests - end - - it 'displays the blob using the rich viewer' do - aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') - - # shows rendered Markdown - expect(page).to have_link("PEP-8") - - # shows a viewer switcher - expect(page).to have_selector('.js-blob-viewer-switcher') - - # shows a disabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn.disabled') - - # shows a raw button - expect(page).to have_link('Open raw') - - # shows a download button - expect(page).to have_link('Download') - end - end - - context 'switching to the simple viewer' do - before do - find('.js-blob-viewer-switch-btn[data-viewer=simple]').click - - wait_for_requests - end - - it 'displays the blob using the simple viewer' do - aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) - - # shows highlighted Markdown code - expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - end - end - - context 'switching to the rich viewer again' do - before do - find('.js-blob-viewer-switch-btn[data-viewer=rich]').click - - wait_for_requests - end - - it 'displays the blob using the rich viewer' do - aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - end - end - end - end - end - - context 'visiting with a line number anchor' do - before do - visit project_snippet_path(project, snippet, anchor: 'L1') - - wait_for_requests - end - - it 'displays the blob using the simple viewer' do - aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) - - # highlights the line in question - expect(page).to have_selector('#LC1.hll') - - # shows highlighted Markdown code - expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") + it_behaves_like 'showing user status' do + let(:file_path) { 'files/ruby/popen.rb' } + let(:user_with_status) { snippet.author } - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - end - end - end + subject { visit project_snippet_path(project, snippet) } end - it_behaves_like 'showing user status' do - let(:file_name) { 'ruby-style-guide.md' } - let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data } - - let(:user_with_status) { snippet.author } + it_behaves_like 'does not show New Snippet button' do + let(:file_path) { 'files/ruby/popen.rb' } - subject do - visit project_snippet_path(project, snippet) - wait_for_requests - end + subject { visit project_snippet_path(project, snippet) } end end diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index 9125ed74273..981ed12d540 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -3,180 +3,33 @@ require 'spec_helper' RSpec.describe 'Snippet', :js do - let(:project) { create(:project, :repository) } - let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) } + let_it_be(:user) { create(:user) } + let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) } before do stub_feature_flags(snippets_vue: false) end - context 'Ruby file' do - let(:file_name) { 'popen.rb' } - let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data } + it_behaves_like 'show and render proper snippet blob' do + let(:anchor) { nil } - before do - visit snippet_path(snippet) + subject do + visit snippet_path(snippet, anchor: anchor) wait_for_requests end - - it 'displays the blob' do - aggregate_failures do - # shows highlighted Ruby code - expect(page).to have_content("require 'fileutils'") - - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - - # shows a raw button - expect(page).to have_link('Open raw') - - # shows a download button - expect(page).to have_link('Download') - end - end - end - - context 'Markdown file' do - let(:file_name) { 'ruby-style-guide.md' } - let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data } - - context 'visiting directly' do - before do - visit snippet_path(snippet) - - wait_for_requests - end - - it 'displays the blob using the rich viewer' do - aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') - - # shows rendered Markdown - expect(page).to have_link("PEP-8") - - # shows a viewer switcher - expect(page).to have_selector('.js-blob-viewer-switcher') - - # shows a disabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn.disabled') - - # shows a raw button - expect(page).to have_link('Open raw') - - # shows a download button - expect(page).to have_link('Download') - end - end - - context 'Markdown rendering' do - let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) } - let(:file_name) { 'test.md' } - let(:content) { "1. one\n - sublist\n" } - - context 'when rendering default markdown' do - it 'renders using CommonMark' do - expect(page).to have_content("sublist") - expect(page).not_to have_xpath("//ol//li//ul") - end - end - end - - context 'switching to the simple viewer' do - before do - find('.js-blob-viewer-switch-btn[data-viewer=simple]').click - - wait_for_requests - end - - it 'displays the blob using the simple viewer' do - aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) - - # shows highlighted Markdown code - expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - end - end - - context 'switching to the rich viewer again' do - before do - find('.js-blob-viewer-switch-btn[data-viewer=rich]').click - - wait_for_requests - end - - it 'displays the blob using the rich viewer' do - aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - end - end - end - end - end - - context 'visiting with a line number anchor' do - before do - visit snippet_path(snippet, anchor: 'L1') - - wait_for_requests - end - - it 'displays the blob using the simple viewer' do - aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) - - # highlights the line in question - expect(page).to have_selector('#LC1.hll') - - # shows highlighted Markdown code - expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - end - end - end end it_behaves_like 'showing user status' do - let(:file_name) { 'popen.rb' } - let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data } + let(:file_path) { 'files/ruby/popen.rb' } let(:user_with_status) { snippet.author } subject { visit snippet_path(snippet) } end - context 'when user cannot create snippets' do - let(:user) { create(:user, :external) } - let(:snippet) { create(:personal_snippet, :public) } - - before do - sign_in(user) - - visit snippet_path(snippet) + it_behaves_like 'does not show New Snippet button' do + let(:file_path) { 'files/ruby/popen.rb' } - wait_for_requests - end - - it 'does not show the "New Snippet" button' do - expect(page).not_to have_link('New snippet') - end + subject { visit snippet_path(snippet) } end end diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js index d51fbe5fda5..235f86ae533 100644 --- a/spec/frontend/alert_management/components/alert_management_table_spec.js +++ b/spec/frontend/alert_management/components/alert_management_table_spec.js @@ -13,6 +13,7 @@ import { GlSearchBoxByType, } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; +import waitForPromises from 'helpers/wait_for_promises'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import AlertManagementTable from '~/alert_management/components/alert_management_table.vue'; import { ALERTS_STATUS_TABS, trackAlertStatusUpdateOptions } from '~/alert_management/constants'; @@ -44,6 +45,7 @@ describe('AlertManagementTable', () => { const findPagination = () => wrapper.find(GlPagination); const findSearch = () => wrapper.find(GlSearchBoxByType); const findIssueFields = () => wrapper.findAll('[data-testid="issueField"]'); + const findAlertError = () => wrapper.find('[data-testid="alert-error"]'); const alertsCount = { open: 14, triggered: 10, @@ -51,6 +53,11 @@ describe('AlertManagementTable', () => { resolved: 1, all: 16, }; + const selectFirstStatusOption = () => { + findFirstStatusOption().vm.$emit('click'); + + return waitForPromises(); + }; function mountComponent({ props = { @@ -138,7 +145,7 @@ describe('AlertManagementTable', () => { it('error state', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { errors: ['error'] }, alertsCount: null, errored: true }, + data: { alerts: { errors: ['error'] }, alertsCount: null, hasError: true }, loading: false, }); expect(findAlertsTable().exists()).toBe(true); @@ -155,7 +162,7 @@ describe('AlertManagementTable', () => { it('empty state', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: [], pageInfo: {} }, alertsCount: { all: 0 }, errored: false }, + data: { alerts: { list: [], pageInfo: {} }, alertsCount: { all: 0 }, hasError: false }, loading: false, }); expect(findAlertsTable().exists()).toBe(true); @@ -172,7 +179,7 @@ describe('AlertManagementTable', () => { it('has data state', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); expect(findLoader().exists()).toBe(false); @@ -188,7 +195,7 @@ describe('AlertManagementTable', () => { it('displays status dropdown', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); expect(findStatusDropdown().exists()).toBe(true); @@ -197,7 +204,7 @@ describe('AlertManagementTable', () => { it('does not display a dropdown status header', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); expect(findStatusDropdown().contains('.dropdown-title')).toBe(false); @@ -206,7 +213,7 @@ describe('AlertManagementTable', () => { it('shows correct severity icons', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); @@ -223,7 +230,7 @@ describe('AlertManagementTable', () => { it('renders severity text', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); @@ -237,7 +244,7 @@ describe('AlertManagementTable', () => { it('renders Unassigned when no assignee(s) present', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); @@ -251,7 +258,7 @@ describe('AlertManagementTable', () => { it('renders username(s) when assignee(s) present', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); @@ -265,7 +272,7 @@ describe('AlertManagementTable', () => { it('navigates to the detail page when alert row is clicked', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); @@ -279,7 +286,7 @@ describe('AlertManagementTable', () => { beforeEach(() => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); }); @@ -323,7 +330,7 @@ describe('AlertManagementTable', () => { ], }, alertsCount, - errored: false, + hasError: false, }, loading: false, }); @@ -343,7 +350,7 @@ describe('AlertManagementTable', () => { }, ], alertsCount, - errored: false, + hasError: false, }, loading: false, }); @@ -358,7 +365,7 @@ describe('AlertManagementTable', () => { it('should highlight the row when alert is new', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: [newAlert] }, alertsCount, errored: false }, + data: { alerts: { list: [newAlert] }, alertsCount, hasError: false }, loading: false, }); @@ -372,7 +379,7 @@ describe('AlertManagementTable', () => { it('should not highlight the row when alert is not new', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: [oldAlert] }, alertsCount, errored: false }, + data: { alerts: { list: [oldAlert] }, alertsCount, hasError: false }, loading: false, }); @@ -392,7 +399,7 @@ describe('AlertManagementTable', () => { props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, data: { alerts: { list: mockAlerts }, - errored: false, + hasError: false, sort: 'STARTED_AT_DESC', alertsCount, }, @@ -429,7 +436,7 @@ describe('AlertManagementTable', () => { beforeEach(() => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); }); @@ -448,19 +455,36 @@ describe('AlertManagementTable', () => { }); }); - it('shows an error when request fails', () => { - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); - findFirstStatusOption().vm.$emit('click'); - wrapper.setData({ - errored: true, + describe('when a request fails', () => { + beforeEach(() => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); }); - return wrapper.vm.$nextTick(() => { - expect(wrapper.find('[data-testid="alert-error"]').exists()).toBe(true); + it('shows an error', async () => { + await selectFirstStatusOption(); + + expect(findAlertError().text()).toContain( + 'There was an error while updating the status of the alert.', + ); + }); + + it('shows an error when triggered a second time', async () => { + await selectFirstStatusOption(); + + wrapper.find(GlAlert).vm.$emit('dismiss'); + + await wrapper.vm.$nextTick(); + + // Assert that the error has been dismissed in the setup + expect(findAlertError().exists()).toBe(false); + + await selectFirstStatusOption(); + + expect(findAlertError().exists()).toBe(true); }); }); - it('shows an error when response includes HTML errors', () => { + it('shows an error when response includes HTML errors', async () => { const mockUpdatedMutationErrorResult = { data: { updateAlertStatus: { @@ -474,13 +498,11 @@ describe('AlertManagementTable', () => { }; jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationErrorResult); - findFirstStatusOption().vm.$emit('click'); - wrapper.setData({ errored: true }); - return wrapper.vm.$nextTick(() => { - expect(wrapper.contains('[data-testid="alert-error"]')).toBe(true); - expect(wrapper.contains('[data-testid="htmlError"]')).toBe(true); - }); + await selectFirstStatusOption(); + + expect(findAlertError().exists()).toBe(true); + expect(findAlertError().contains('[data-testid="htmlError"]')).toBe(true); }); }); @@ -510,7 +532,7 @@ describe('AlertManagementTable', () => { beforeEach(() => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts, pageInfo: {} }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts, pageInfo: {} }, alertsCount, hasError: false }, loading: false, }); }); @@ -570,7 +592,7 @@ describe('AlertManagementTable', () => { beforeEach(() => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + data: { alerts: { list: mockAlerts }, alertsCount, hasError: false }, loading: false, }); }); diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js index e067ef744fd..4a17fb7393a 100644 --- a/spec/frontend/incidents/components/incidents_list_spec.js +++ b/spec/frontend/incidents/components/incidents_list_spec.js @@ -6,11 +6,12 @@ import { GlAvatar, GlPagination, GlSearchBoxByType, + GlTab, } from '@gitlab/ui'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import IncidentsList from '~/incidents/components/incidents_list.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; -import { I18N } from '~/incidents/constants'; +import { I18N, INCIDENT_STATE_TABS } from '~/incidents/constants'; import mockIncidents from '../mocks/incidents.json'; jest.mock('~/lib/utils/url_utility', () => ({ @@ -34,6 +35,7 @@ describe('Incidents List', () => { const findSearch = () => wrapper.find(GlSearchBoxByType); const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']"); const findPagination = () => wrapper.find(GlPagination); + const findStatusFilterTabs = () => wrapper.findAll(GlTab); function mountComponent({ data = { incidents: [] }, loading = false }) { wrapper = mount(IncidentsList, { @@ -280,5 +282,25 @@ describe('Incidents List', () => { expect(wrapper.vm.$data.searchTerm).toBe(SEARCH_TERM); }); }); + + describe('State Filter Tabs', () => { + beforeEach(() => { + mountComponent({ + data: { incidents: mockIncidents }, + loading: false, + stubs: { + GlTab: true, + }, + }); + }); + + it('should display filter tabs', () => { + const tabs = findStatusFilterTabs().wrappers; + + tabs.forEach((tab, i) => { + expect(tab.attributes('data-testid')).toContain(INCIDENT_STATE_TABS[i].state); + }); + }); + }); }); }); diff --git a/spec/graphql/mutations/issues/set_subscription_spec.rb b/spec/graphql/mutations/issues/set_subscription_spec.rb new file mode 100644 index 00000000000..9e05a136c0b --- /dev/null +++ b/spec/graphql/mutations/issues/set_subscription_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Issues::SetSubscription do + it_behaves_like 'a subscribeable graphql resource' do + let_it_be(:resource) { create(:issue) } + let(:permission_name) { :update_issue } + end +end diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb index 20cfed9dd3d..600053637c9 100644 --- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -3,44 +3,8 @@ require 'spec_helper' RSpec.describe Mutations::MergeRequests::SetSubscription do - let(:merge_request) { create(:merge_request) } - let(:project) { merge_request.project } - let(:user) { create(:user) } - - subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } - - specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } - - describe '#resolve' do - let(:subscribe) { true } - let(:mutated_merge_request) { subject[:merge_request] } - - subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, subscribed_state: subscribe) } - - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - - context 'when the user can update the merge request' do - before do - merge_request.project.add_developer(user) - end - - it 'returns the merge request as discussion locked' do - expect(mutated_merge_request).to eq(merge_request) - expect(mutated_merge_request.subscribed?(user, project)).to eq(true) - expect(subject[:errors]).to be_empty - end - - context 'when passing subscribe as false' do - let(:subscribe) { false } - - it 'unsubscribes from the discussion' do - merge_request.subscribe(user, project) - - expect(mutated_merge_request.subscribed?(user, project)).to eq(false) - end - end - end + it_behaves_like 'a subscribeable graphql resource' do + let_it_be(:resource) { create(:merge_request) } + let(:permission_name) { :update_merge_request } end end diff --git a/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb b/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb new file mode 100644 index 00000000000..f9a56bf649d --- /dev/null +++ b/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20200728080250_replace_unique_index_on_cycle_analytics_stages.rb') + +RSpec.describe ReplaceUniqueIndexOnCycleAnalyticsStages, :migration, schema: 20200728080250 do + let(:namespaces) { table(:namespaces) } + let(:group_value_streams) { table(:analytics_cycle_analytics_group_value_streams) } + let(:group_stages) { table(:analytics_cycle_analytics_group_stages) } + + let(:group) { namespaces.create!(type: 'Group', name: 'test', path: 'test') } + + let(:value_stream_1) { group_value_streams.create!(group_id: group.id, name: 'vs1') } + let(:value_stream_2) { group_value_streams.create!(group_id: group.id, name: 'vs2') } + + let(:duplicated_stage_1) { group_stages.create!(group_id: group.id, group_value_stream_id: value_stream_1.id, name: 'stage', start_event_identifier: 1, end_event_identifier: 1) } + let(:duplicated_stage_2) { group_stages.create!(group_id: group.id, group_value_stream_id: value_stream_2.id, name: 'stage', start_event_identifier: 1, end_event_identifier: 1) } + + let(:stage_record) { group_stages.create!(group_id: group.id, group_value_stream_id: value_stream_2.id, name: 'other stage', start_event_identifier: 1, end_event_identifier: 1) } + + describe '#down' do + subject { described_class.new.down } + + before do + described_class.new.up + + duplicated_stage_1 + duplicated_stage_2 + stage_record + end + + it 'removes duplicated stage records' do + subject + + stage = group_stages.find_by_id(duplicated_stage_2.id) + expect(stage).to be_nil + end + + it 'does not change the first duplicated stage record' do + expect { subject }.not_to change { duplicated_stage_1.reload.attributes } + end + + it 'does not change not duplicated stage record' do + expect { subject }.not_to change { stage_record.reload.attributes } + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d2aa9340a8d..4609ac7fe56 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4125,7 +4125,6 @@ RSpec.describe Project do end it 'removes the pages directory and marks the project as not having pages deployed' do - expect_any_instance_of(Projects::UpdatePagesConfigurationService).to receive(:execute) expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return(true) expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, namespace.full_path, anything) diff --git a/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb new file mode 100644 index 00000000000..1edc1e0553b --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Setting subscribed status of an issue' do + include GraphqlHelpers + + it_behaves_like 'a subscribable resource api' do + let_it_be(:resource) { create(:issue) } + let(:mutation_name) { :issue_set_subscription } + end +end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb index 6b3035fbf48..d90faa605c0 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -5,59 +5,8 @@ require 'spec_helper' RSpec.describe 'Setting subscribed status of a merge request' do include GraphqlHelpers - let(:current_user) { create(:user) } - let(:merge_request) { create(:merge_request) } - let(:project) { merge_request.project } - let(:input) { { subscribed_state: true } } - - let(:mutation) do - variables = { - project_path: project.full_path, - iid: merge_request.iid.to_s - } - graphql_mutation(:merge_request_set_subscription, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - subscribed - } - QL - ) - end - - def mutation_response - graphql_mutation_response(:merge_request_set_subscription)['mergeRequest']['subscribed'] - end - - before do - project.add_developer(current_user) - end - - it 'returns an error if the user is not allowed to update the merge request' do - post_graphql_mutation(mutation, current_user: create(:user)) - - expect(graphql_errors).not_to be_empty - end - - it 'marks the merge request as WIP' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response).to eq(true) - end - - context 'when passing subscribe false as input' do - let(:input) { { subscribed_state: false } } - - it 'unmarks the merge request as subscribed' do - merge_request.subscribe(current_user, project) - - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response).to eq(false) - end + it_behaves_like 'a subscribable resource api' do + let_it_be(:resource) { create(:merge_request) } + let(:mutation_name) { :merge_request_set_subscription } end end diff --git a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb index ee3c55cb642..5263fd40a40 100644 --- a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do include ReactiveCachingHelpers include GrafanaApiHelpers - let_it_be(:project) { build(:project) } + let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:grafana_integration) { create(:grafana_integration, project: project) } @@ -15,7 +15,7 @@ RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do valid_grafana_dashboard_link(grafana_integration.grafana_url) end - before do + before_all do project.add_maintainer(user) end @@ -58,6 +58,31 @@ RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do expect(subject.current_user).to eq(user) expect(subject.params[:grafana_url]).to eq(grafana_url) end + + context 'with unknown users' do + let(:params) { [project.id, current_user_id, grafana_url] } + + context 'when anonymous' do + where(:current_user_id) do + [nil, ''] + end + + with_them do + it 'sets current_user as nil' do + expect(subject.current_user).to be_nil + end + end + end + + context 'when invalid' do + let(:current_user_id) { non_existing_record_id } + + it 'raise record not found error' do + expect { subject } + .to raise_error(ActiveRecord::RecordNotFound, /Couldn't find User/) + end + end + end end describe '#get_dashboard', :use_clean_rails_memory_store_caching do @@ -145,7 +170,17 @@ RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do stub_datasource_request(grafana_integration.grafana_url) end - it_behaves_like 'valid embedded dashboard service response' + context 'when project is private and user is member' do + it_behaves_like 'valid embedded dashboard service response' + end + + context 'when project is public and user is anonymous' do + let(:project) { create(:project, :public) } + let(:user) { nil } + let(:grafana_integration) { create(:grafana_integration, project: project) } + + it_behaves_like 'valid embedded dashboard service response' + end end end diff --git a/spec/support/shared_examples/features/snippets_shared_examples.rb b/spec/support/shared_examples/features/snippets_shared_examples.rb index 1c8a9714bdf..0ac0dc72017 100644 --- a/spec/support/shared_examples/features/snippets_shared_examples.rb +++ b/spec/support/shared_examples/features/snippets_shared_examples.rb @@ -50,3 +50,145 @@ RSpec.shared_examples 'tabs with counts' do expect(tab.find('.badge').text).to eq(counts[:public]) end end + +RSpec.shared_examples 'does not show New Snippet button' do + let(:user) { create(:user, :external) } + + specify do + sign_in(user) + + subject + + wait_for_requests + + expect(page).not_to have_link('New snippet') + end +end + +RSpec.shared_examples 'show and render proper snippet blob' do + before do + allow_any_instance_of(Snippet).to receive(:blobs).and_return([snippet.repository.blob_at('master', file_path)]) + end + + context 'Ruby file' do + let(:file_path) { 'files/ruby/popen.rb' } + + it 'displays the blob' do + subject + + aggregate_failures do + # shows highlighted Ruby code + expect(page).to have_content("require 'fileutils'") + + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') + + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + + # shows a raw button + expect(page).to have_link('Open raw') + + # shows a download button + expect(page).to have_link('Download') + end + end + end + + context 'Markdown file' do + let(:file_path) { 'files/markdown/ruby-style-guide.md' } + + context 'visiting directly' do + before do + subject + end + + it 'displays the blob using the rich viewer' do + aggregate_failures do + # hides the simple viewer + expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) + expect(page).to have_selector('.blob-viewer[data-type="rich"]') + + # shows rendered Markdown + expect(page).to have_link("PEP-8") + + # shows a viewer switcher + expect(page).to have_selector('.js-blob-viewer-switcher') + + # shows a disabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn.disabled') + + # shows a raw button + expect(page).to have_link('Open raw') + + # shows a download button + expect(page).to have_link('Download') + end + end + + context 'switching to the simple viewer' do + before do + find('.js-blob-viewer-switch-btn[data-viewer=simple]').click + + wait_for_requests + end + + it 'displays the blob using the simple viewer' do + aggregate_failures do + # hides the rich viewer + expect(page).to have_selector('.blob-viewer[data-type="simple"]') + expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) + + # shows highlighted Markdown code + expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") + + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + end + end + + context 'switching to the rich viewer again' do + before do + find('.js-blob-viewer-switch-btn[data-viewer=rich]').click + + wait_for_requests + end + + it 'displays the blob using the rich viewer' do + aggregate_failures do + # hides the simple viewer + expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) + expect(page).to have_selector('.blob-viewer[data-type="rich"]') + + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + end + end + end + end + end + + context 'visiting with a line number anchor' do + let(:anchor) { 'L1' } + + it 'displays the blob using the simple viewer' do + subject + + aggregate_failures do + # hides the rich viewer + expect(page).to have_selector('.blob-viewer[data-type="simple"]') + expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) + + # highlights the line in question + expect(page).to have_selector('#LC1.hll') + + # shows highlighted Markdown code + expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") + + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + end + end + end + end +end diff --git a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb new file mode 100644 index 00000000000..ebba312e895 --- /dev/null +++ b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'a subscribeable graphql resource' do + let(:project) { resource.project } + let_it_be(:user) { create(:user) } + + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + specify { expect(described_class).to require_graphql_authorizations(permission_name) } + + describe '#resolve' do + let(:subscribe) { true } + let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } + + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } + + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + + context 'when the user can update the resource' do + before do + resource.project.add_developer(user) + end + + it 'subscribes to the resource' do + expect(mutated_resource).to eq(resource) + expect(mutated_resource.subscribed?(user, project)).to eq(true) + expect(subject[:errors]).to be_empty + end + + context 'when passing subscribe as false' do + let(:subscribe) { false } + + it 'unsubscribes from the discussion' do + resource.subscribe(user, project) + + expect(mutated_resource.subscribed?(user, project)).to eq(false) + end + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb new file mode 100644 index 00000000000..40b88ef370f --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'a subscribable resource api' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let(:project) { resource.project } + let(:input) { { subscribed_state: true } } + let(:resource_ref) { resource.class.name.camelize(:lower) } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: resource.iid.to_s + } + + graphql_mutation( + mutation_name, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + #{resource_ref} { + id + subscribed + } + QL + ) + end + + def mutation_response + graphql_mutation_response(mutation_name)[resource_ref]['subscribed'] + end + + context 'when the user is not authorized' do + it_behaves_like 'a mutation that returns top-level errors', + errors: ["The resource that you are attempting to access "\ + "does not exist or you don't have permission to "\ + "perform this action"] + end + + context 'when user is authorized' do + before do + project.add_developer(current_user) + end + + it 'marks the resource as subscribed' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response).to eq(true) + end + + context 'when passing subscribe false as input' do + let(:input) { { subscribed_state: false } } + + it 'unmarks the resource as subscribed' do + resource.subscribe(current_user, project) + + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response).to eq(false) + end + end + end +end diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb index c8fabfe30b9..6f131a75e3d 100644 --- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb +++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb @@ -78,6 +78,12 @@ RSpec.shared_examples 'raises error for users with insufficient permissions' do it_behaves_like 'misconfigured dashboard service response', :unauthorized end + + context 'when the user is anonymous' do + let(:user) { nil } + + it_behaves_like 'misconfigured dashboard service response', :unauthorized + end end RSpec.shared_examples 'valid dashboard cloning process' do |dashboard_template, sequence| diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index cb6396e2859..df7acd2040f 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -11,31 +11,57 @@ RSpec.describe GitGarbageCollectWorker do let(:shell) { Gitlab::Shell.new } let!(:lease_uuid) { SecureRandom.uuid } let!(:lease_key) { "project_housekeeping:#{project.id}" } + let(:params) { [project.id, task, lease_key, lease_uuid] } subject { described_class.new } + shared_examples 'it calls Gitaly' do + specify do + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(gitaly_task) + .and_return(nil) + + subject.perform(*params) + end + end + + shared_examples 'it updates the project statistics' do + specify do + expect_any_instance_of(Projects::UpdateStatisticsService).to receive(:execute).and_call_original + expect(Projects::UpdateStatisticsService) + .to receive(:new) + .with(project, nil, statistics: [:repository_size, :lfs_objects_size]) + .and_call_original + + subject.perform(*params) + end + end + describe "#perform" do + let(:gitaly_task) { :garbage_collect } + let(:task) { :gc } + context 'with active lease_uuid' do before do allow(subject).to receive(:get_lease_uuid).and_return(lease_uuid) end + it_behaves_like 'it calls Gitaly' + it_behaves_like 'it updates the project statistics' + it "flushes ref caches when the task if 'gc'" do expect(subject).to receive(:renew_lease).with(lease_key, lease_uuid).and_call_original - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect) - .and_return(nil) expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original - subject.perform(project.id, :gc, lease_key, lease_uuid) + subject.perform(*params) end it 'handles gRPC errors' do expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect).and_raise(GRPC::NotFound) - expect { subject.perform(project.id, :gc, lease_key, lease_uuid) }.to raise_exception(Gitlab::Git::Repository::NoRepository) + expect { subject.perform(*params) }.to raise_exception(Gitlab::Git::Repository::NoRepository) end end @@ -49,11 +75,13 @@ RSpec.describe GitGarbageCollectWorker do expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original - subject.perform(project.id, :gc, lease_key, lease_uuid) + subject.perform(*params) end end context 'with no active lease' do + let(:params) { [project.id] } + before do allow(subject).to receive(:get_lease_uuid).and_return(false) end @@ -63,15 +91,16 @@ RSpec.describe GitGarbageCollectWorker do allow(subject).to receive(:try_obtain_lease).and_return(SecureRandom.uuid) end + it_behaves_like 'it calls Gitaly' + it_behaves_like 'it updates the project statistics' + it "flushes ref caches when the task if 'gc'" do - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect) - .and_return(nil) expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original - subject.perform(project.id) + subject.perform(*params) end context 'when the repository has joined a pool' do @@ -81,7 +110,7 @@ RSpec.describe GitGarbageCollectWorker do it 'ensures the repositories are linked' do expect_any_instance_of(PoolRepository).to receive(:link_repository).once - subject.perform(project.id) + subject.perform(*params) end end end @@ -97,48 +126,55 @@ RSpec.describe GitGarbageCollectWorker do expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original - subject.perform(project.id) + subject.perform(*params) end end end context "repack_full" do + let(:task) { :full_repack } + let(:gitaly_task) { :repack_full } + before do expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid) end - it "calls Gitaly" do - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:repack_full) - .and_return(nil) - - subject.perform(project.id, :full_repack, lease_key, lease_uuid) - end + it_behaves_like 'it calls Gitaly' + it_behaves_like 'it updates the project statistics' end context "pack_refs" do + let(:task) { :pack_refs } + let(:gitaly_task) { :pack_refs } + before do expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid) end it "calls Gitaly" do - expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:pack_refs) + expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(task) .and_return(nil) - subject.perform(project.id, :pack_refs, lease_key, lease_uuid) + subject.perform(*params) + end + + it 'does not update the project statistics' do + expect(Projects::UpdateStatisticsService).not_to receive(:new) + + subject.perform(*params) end end context "repack_incremental" do + let(:task) { :incremental_repack } + let(:gitaly_task) { :repack_incremental } + before do expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid) end - it "calls Gitaly" do - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:repack_incremental) - .and_return(nil) - - subject.perform(project.id, :incremental_repack, lease_key, lease_uuid) - end + it_behaves_like 'it calls Gitaly' + it_behaves_like 'it updates the project statistics' end shared_examples 'gc tasks' do |