Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f645d7e060
commit
8f71e69fdb
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import {
|
||||
GlAlert,
|
||||
GlBadge,
|
||||
|
@ -12,6 +11,7 @@ import {
|
|||
GlButton,
|
||||
GlSafeHtmlDirective,
|
||||
} from '@gitlab/ui';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import { s__ } from '~/locale';
|
||||
import alertQuery from '../graphql/queries/details.query.graphql';
|
||||
import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
|
||||
|
@ -30,6 +30,7 @@ import AlertSidebar from './alert_sidebar.vue';
|
|||
import AlertMetrics from './alert_metrics.vue';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import AlertSummaryRow from './alert_summary_row.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
const containerEl = document.querySelector('.page-with-contextual-sidebar');
|
||||
|
||||
|
@ -76,6 +77,7 @@ export default {
|
|||
SystemNote,
|
||||
AlertMetrics,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
inject: {
|
||||
projectPath: {
|
||||
default: '',
|
||||
|
@ -147,6 +149,15 @@ export default {
|
|||
this.$router.replace({ name: 'tab', params: { tabId } });
|
||||
},
|
||||
},
|
||||
environmentName() {
|
||||
return this.shouldDisplayEnvironment && this.alert?.environment?.name;
|
||||
},
|
||||
environmentPath() {
|
||||
return this.shouldDisplayEnvironment && this.alert?.environment?.path;
|
||||
},
|
||||
shouldDisplayEnvironment() {
|
||||
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.trackPageViews();
|
||||
|
@ -299,19 +310,18 @@ export default {
|
|||
</span>
|
||||
</alert-summary-row>
|
||||
<alert-summary-row
|
||||
v-if="alert.environment"
|
||||
v-if="environmentName"
|
||||
:label="`${s__('AlertManagement|Environment')}:`"
|
||||
>
|
||||
<gl-link
|
||||
v-if="alert.environmentUrl"
|
||||
v-if="environmentPath"
|
||||
class="gl-display-inline-block"
|
||||
data-testid="environmentUrl"
|
||||
:href="alert.environmentUrl"
|
||||
target="_blank"
|
||||
data-testid="environmentPath"
|
||||
:href="environmentPath"
|
||||
>
|
||||
{{ alert.environment }}
|
||||
{{ environmentName }}
|
||||
</gl-link>
|
||||
<span v-else data-testid="environment">{{ alert.environment }}</span>
|
||||
<span v-else data-testid="environmentName">{{ environmentName }}</span>
|
||||
</alert-summary-row>
|
||||
<alert-summary-row
|
||||
v-if="alert.startedAt"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
|
||||
import produce from 'immer';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import produce from 'immer';
|
||||
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import createRouter from './router';
|
||||
import AlertDetails from './components/alert_details.vue';
|
||||
import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql';
|
||||
import createRouter from './router';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ fragment AlertDetailItem on AlertManagementAlert {
|
|||
updatedAt
|
||||
endedAt
|
||||
hosts
|
||||
environment {
|
||||
name
|
||||
path
|
||||
}
|
||||
details
|
||||
runbook
|
||||
todos {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import { s__ } from '~/locale';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { deprecatedCreateFlash as flash } from '~/flash';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import { escape } from 'lodash';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import { spriteIcon } from './lib/utils/common_utils';
|
||||
|
||||
const FLASH_TYPES = {
|
||||
|
|
|
@ -11,6 +11,8 @@ export default {
|
|||
GlTab,
|
||||
AlertsSettingsForm,
|
||||
PagerDutySettingsForm,
|
||||
ServiceLevelAgreementForm: () =>
|
||||
import('ee_component/incidents_settings/components/service_level_agreement_form.vue'),
|
||||
},
|
||||
tabs: INTEGRATION_TABS_CONFIG,
|
||||
i18n: I18N_INTEGRATION_TABS,
|
||||
|
@ -45,6 +47,7 @@ export default {
|
|||
>
|
||||
<component :is="tab.component" class="gl-pt-3" :data-testid="`${tab.component}-tab`" />
|
||||
</gl-tab>
|
||||
<service-level-agreement-form />
|
||||
</gl-tabs>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -21,6 +21,9 @@ export default () => {
|
|||
pagerdutyWebhookUrl,
|
||||
pagerdutyResetKeyPath,
|
||||
autoCloseIncident,
|
||||
slaActive,
|
||||
slaMinutes,
|
||||
slaFeatureAvailable,
|
||||
},
|
||||
} = el;
|
||||
|
||||
|
@ -40,6 +43,11 @@ export default () => {
|
|||
active: parseBoolean(pagerdutyActive),
|
||||
webhookUrl: pagerdutyWebhookUrl,
|
||||
},
|
||||
serviceLevelAgreementSettings: {
|
||||
active: parseBoolean(slaActive),
|
||||
minutes: slaMinutes,
|
||||
available: parseBoolean(slaFeatureAvailable),
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(SettingsTabs);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
|
||||
// We currently load + parse the data from the issue app and related merge request
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import * as types from './mutation_types';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import * as types from './mutation_types';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import $ from 'jquery';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
const IGNORE_ERRORS = [
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179
|
||||
// export * from '@sentry/browser';
|
||||
|
||||
export function init(...args) {
|
||||
return args;
|
||||
}
|
||||
|
||||
export function setUser(...args) {
|
||||
return args;
|
||||
}
|
||||
|
||||
export function captureException(...args) {
|
||||
return args;
|
||||
}
|
||||
|
||||
export function captureMessage(...args) {
|
||||
return args;
|
||||
}
|
||||
|
||||
export function withScope(fn) {
|
||||
fn({
|
||||
setTag(...args) {
|
||||
return args;
|
||||
},
|
||||
});
|
||||
}
|
|
@ -7,9 +7,11 @@ import {
|
|||
convertToSentenceCase,
|
||||
splitCamelCase,
|
||||
} from '~/lib/utils/text_utility';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
const thClass = 'gl-bg-transparent! gl-border-1! gl-border-b-solid! gl-border-gray-200!';
|
||||
const tdClass = 'gl-border-gray-100! gl-p-5!';
|
||||
|
||||
const allowedFields = [
|
||||
'iid',
|
||||
'title',
|
||||
|
@ -22,17 +24,15 @@ const allowedFields = [
|
|||
'description',
|
||||
'endedAt',
|
||||
'details',
|
||||
'environment',
|
||||
'hosts',
|
||||
];
|
||||
|
||||
const isAllowed = fieldName => allowedFields.includes(fieldName);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
GlTable,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
alert: {
|
||||
type: Object,
|
||||
|
@ -60,14 +60,23 @@ export default {
|
|||
},
|
||||
],
|
||||
computed: {
|
||||
flaggedAllowedFields() {
|
||||
return this.shouldDisplayEnvironment ? [...allowedFields, 'environment'] : allowedFields;
|
||||
},
|
||||
items() {
|
||||
if (!this.alert) {
|
||||
return [];
|
||||
}
|
||||
return reduce(
|
||||
this.alert,
|
||||
(allowedItems, value, fieldName) => {
|
||||
if (isAllowed(fieldName)) {
|
||||
(allowedItems, fieldValue, fieldName) => {
|
||||
if (this.isAllowed(fieldName)) {
|
||||
let value;
|
||||
if (fieldName === 'environment') {
|
||||
value = fieldValue?.name;
|
||||
} else {
|
||||
value = fieldValue;
|
||||
}
|
||||
return [...allowedItems, { fieldName, value }];
|
||||
}
|
||||
return allowedItems;
|
||||
|
@ -75,6 +84,14 @@ export default {
|
|||
[],
|
||||
);
|
||||
},
|
||||
shouldDisplayEnvironment() {
|
||||
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isAllowed(fieldName) {
|
||||
return this.flaggedAllowedFields.includes(fieldName);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -32,8 +32,8 @@ export default {
|
|||
uploadImageTab: null,
|
||||
};
|
||||
},
|
||||
modalTitle: __('Image Details'),
|
||||
okTitle: __('Insert'),
|
||||
modalTitle: __('Image details'),
|
||||
okTitle: __('Insert image'),
|
||||
urlTabTitle: __('By URL'),
|
||||
urlLabel: __('Image URL'),
|
||||
descriptionLabel: __('Description'),
|
||||
|
|
|
@ -247,6 +247,7 @@
|
|||
|
||||
.label-badge {
|
||||
color: $gray-900;
|
||||
display: inline-block;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
padding: $gl-padding-4 $gl-padding-8;
|
||||
border-radius: $border-radius-default;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
class Admin::InstanceReviewController < Admin::ApplicationController
|
||||
feature_category :instance_statistics
|
||||
|
||||
def index
|
||||
redirect_to("#{::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL}/instance_review?#{instance_review_params}")
|
||||
end
|
||||
|
||||
def instance_review_params
|
||||
result = {
|
||||
instance_review: {
|
||||
email: current_user.email,
|
||||
last_name: current_user.name,
|
||||
version: ::Gitlab::VERSION
|
||||
}
|
||||
}
|
||||
|
||||
if Gitlab::CurrentSettings.usage_ping_enabled?
|
||||
data = ::Gitlab::UsageData.data
|
||||
counts = data[:counts]
|
||||
|
||||
result[:instance_review].merge!(
|
||||
users_count: data[:active_user_count],
|
||||
projects_count: counts[:projects],
|
||||
groups_count: counts[:groups],
|
||||
issues_count: counts[:issues],
|
||||
merge_requests_count: counts[:merge_requests],
|
||||
internal_pipelines_count: counts[:ci_internal_pipelines],
|
||||
external_pipelines_count: counts[:ci_external_pipelines],
|
||||
labels_count: counts[:labels],
|
||||
milestones_count: counts[:milestones],
|
||||
snippets_count: counts[:snippets],
|
||||
notes_count: counts[:notes]
|
||||
)
|
||||
end
|
||||
|
||||
result.to_query
|
||||
end
|
||||
end
|
|
@ -80,18 +80,17 @@ class HelpController < ApplicationController
|
|||
def documentation_url
|
||||
return unless documentation_base_url
|
||||
|
||||
@documentation_url ||= [
|
||||
documentation_base_url.chomp('/'),
|
||||
version_segment,
|
||||
'ee',
|
||||
"#{@path}.html"
|
||||
].compact.join('/')
|
||||
@documentation_url ||= Gitlab::Utils.append_path(documentation_base_url, documentation_file_path)
|
||||
end
|
||||
|
||||
def documentation_base_url
|
||||
@documentation_base_url ||= Gitlab::CurrentSettings.current_application_settings.help_page_documentation_base_url.presence
|
||||
end
|
||||
|
||||
def documentation_file_path
|
||||
@documentation_file_path ||= [version_segment, 'ee', "#{@path}.html"].compact.join('/')
|
||||
end
|
||||
|
||||
def version_segment
|
||||
return if Gitlab.pre_release?
|
||||
|
||||
|
|
|
@ -10,5 +10,6 @@ class Projects::AlertManagementController < Projects::ApplicationController
|
|||
|
||||
def details
|
||||
@alert_id = params[:id]
|
||||
push_frontend_feature_flag(:expose_environment_path_in_alert_details, @project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,6 +68,11 @@ module Types
|
|||
null: true,
|
||||
description: 'Timestamp the alert ended'
|
||||
|
||||
field :environment,
|
||||
Types::EnvironmentType,
|
||||
null: true,
|
||||
description: 'Environment for the alert'
|
||||
|
||||
field :event_count,
|
||||
GraphQL::INT_TYPE,
|
||||
null: true,
|
||||
|
|
|
@ -5,6 +5,8 @@ module Types
|
|||
graphql_name 'Environment'
|
||||
description 'Describes where code is deployed for a project'
|
||||
|
||||
present_using ::EnvironmentPresenter
|
||||
|
||||
authorize :read_environment
|
||||
|
||||
field :name, GraphQL::STRING_TYPE, null: false,
|
||||
|
@ -16,6 +18,10 @@ module Types
|
|||
field :state, GraphQL::STRING_TYPE, null: false,
|
||||
description: 'State of the environment, for example: available/stopped'
|
||||
|
||||
field :path, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'The path to the environment. Will always return null ' \
|
||||
'if `expose_environment_path_in_alert_details` feature flag is disabled'
|
||||
|
||||
field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
|
||||
description: 'Metrics dashboard schema for the environment',
|
||||
resolver: Resolvers::Metrics::DashboardResolver
|
||||
|
@ -23,6 +29,6 @@ module Types
|
|||
field :latest_opened_most_severe_alert,
|
||||
Types::AlertManagement::AlertType,
|
||||
null: true,
|
||||
description: 'The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.'
|
||||
description: 'The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -212,6 +212,10 @@ module ApplicationHelper
|
|||
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
|
||||
end
|
||||
|
||||
def instance_review_permitted?
|
||||
::Gitlab::CurrentSettings.instance_review_permitted? && current_user&.admin?
|
||||
end
|
||||
|
||||
def static_objects_external_storage_enabled?
|
||||
Gitlab::CurrentSettings.static_objects_external_storage_enabled?
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ class ApplicationSetting < ApplicationRecord
|
|||
ignore_column :instance_statistics_visibility_private, remove_with: '13.6', remove_after: '2020-10-22'
|
||||
ignore_column :snowplow_iglu_registry_url, remove_with: '13.6', remove_after: '2020-11-22'
|
||||
|
||||
INSTANCE_REVIEW_MIN_USERS = 50
|
||||
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
|
||||
'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'
|
||||
|
||||
|
@ -437,6 +438,14 @@ class ApplicationSetting < ApplicationRecord
|
|||
!!(sourcegraph_url =~ /\Ahttps:\/\/(www\.)?sourcegraph\.com/)
|
||||
end
|
||||
|
||||
def instance_review_permitted?
|
||||
users_count = Rails.cache.fetch('limited_users_count', expires_in: 1.day) do
|
||||
::User.limit(INSTANCE_REVIEW_MIN_USERS + 1).count(:all)
|
||||
end
|
||||
|
||||
users_count >= INSTANCE_REVIEW_MIN_USERS
|
||||
end
|
||||
|
||||
def self.create_from_defaults
|
||||
check_schema!
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ class Environment < ApplicationRecord
|
|||
include Gitlab::Utils::StrongMemoize
|
||||
include ReactiveCaching
|
||||
include FastDestroyAll::Helpers
|
||||
include Presentable
|
||||
|
||||
self.reactive_cache_refresh_interval = 1.minute
|
||||
self.reactive_cache_lifetime = 55.seconds
|
||||
|
|
|
@ -51,3 +51,5 @@ module IncidentManagement
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
IncidentManagement::ProjectIncidentManagementSetting.prepend_if_ee('EE::IncidentManagement::ProjectIncidentManagementSetting')
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EnvironmentPresenter < Gitlab::View::Presenter::Delegated
|
||||
include ActionView::Helpers::UrlHelper
|
||||
|
||||
presents :environment
|
||||
|
||||
def path
|
||||
if Feature.enabled?(:expose_environment_path_in_alert_details, project)
|
||||
project_environment_path(project, self)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class LabelPresenter < Gitlab::View::Presenter::Delegated
|
||||
presents :label
|
||||
delegate :name, :full_name, to: :label_subject, prefix: :subject
|
||||
|
||||
def edit_path
|
||||
case label
|
||||
|
@ -39,8 +40,8 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
|
|||
label.is_a?(ProjectLabel)
|
||||
end
|
||||
|
||||
def subject_name
|
||||
label.subject.name
|
||||
def label_subject
|
||||
@label_subject ||= label.subject
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,6 +7,8 @@ module Users
|
|||
end
|
||||
|
||||
def execute(user)
|
||||
return error('An internal user cannot be blocked', 403) if user.internal?
|
||||
|
||||
if user.block
|
||||
after_block_hook(user)
|
||||
success
|
||||
|
|
|
@ -182,7 +182,7 @@
|
|||
%li Access Git repositories
|
||||
%br
|
||||
= link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: 'Are you sure?' }
|
||||
- else
|
||||
- elsif !@user.internal?
|
||||
.card.border-warning
|
||||
.card-header.bg-warning.text-white
|
||||
Block this user
|
||||
|
|
|
@ -69,7 +69,8 @@
|
|||
= Gon::Base.render_data(nonce: content_security_policy_nonce)
|
||||
|
||||
= javascript_include_tag locale_path unless I18n.locale == :en
|
||||
= webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
|
||||
-# Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179
|
||||
-# = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
|
||||
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
|
||||
|
||||
= yield :page_specific_javascripts
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
|
||||
%li.d-md-none
|
||||
= render 'shared/user_dropdown_contributing_link'
|
||||
= render_if_exists 'shared/user_dropdown_instance_review'
|
||||
= render 'shared/user_dropdown_instance_review'
|
||||
- if Gitlab.com_but_not_canary?
|
||||
%li.d-md-none
|
||||
= link_to _("Switch to GitLab Next"), "https://next.gitlab.com/"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
|
||||
%li
|
||||
= render 'shared/user_dropdown_contributing_link'
|
||||
= render_if_exists 'shared/user_dropdown_instance_review'
|
||||
= render 'shared/user_dropdown_instance_review'
|
||||
- if Gitlab.com_but_not_canary?
|
||||
%li
|
||||
= link_to _("Switch to GitLab Next"), "https://next.gitlab.com/"
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
%li.label-list-item{ id: label_css_id, data: { id: label.id } }
|
||||
= render "shared/label_row", label: label, force_priority: force_priority
|
||||
%ul.label-actions-list
|
||||
- if @project
|
||||
%li.inline
|
||||
.label-badge.gl-bg-gray-50= label.model_name.human.capitalize
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
%li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label),
|
||||
dom_id: dom_id(label), type: label.type } }
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
- full_path = label.subject_full_name
|
||||
|
||||
.label-badge.gl-bg-gray-50.gl-max-w-full.gl-text-truncate{ title: full_path }
|
||||
= full_path
|
|
@ -3,22 +3,28 @@
|
|||
- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues)
|
||||
- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests)
|
||||
|
||||
.label-name.gl-flex-shrink-0.gl-mr-3
|
||||
.label-name.gl-flex-shrink-0.gl-mt-2.gl-mr-3
|
||||
= render_label(label, tooltip: false)
|
||||
.label-description.gl-flex-grow-1.gl-mr-3.gl-w-full
|
||||
- if label.description.present?
|
||||
.description-text.gl-mb-3
|
||||
= markdown_field(label, :description)
|
||||
%ul.label-links.gl-m-0.gl-p-0.gl-white-space-nowrap
|
||||
- if show_label_issues_link
|
||||
%li.inline.gl-text-blue-600
|
||||
= link_to_label(label, css_class: 'gl-text-blue-600!') { _('Issues') }
|
||||
- if show_label_merge_requests_link
|
||||
·
|
||||
%li.inline.gl-text-blue-600
|
||||
= link_to_label(label, type: :merge_request, css_class: 'gl-text-blue-600!') { _('Merge requests') }
|
||||
= render_if_exists 'shared/label_row_epics_link', label: label
|
||||
- if force_priority
|
||||
·
|
||||
%li.js-priority-badge.inline.gl-ml-3
|
||||
.label-badge.gl-bg-blue-50= _('Prioritized label')
|
||||
.label-description.gl-flex-grow-1.gl-overflow-hidden
|
||||
.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-mt-2
|
||||
.description-text.gl-flex-grow-1.gl-overflow-hidden
|
||||
- if label.description.present?
|
||||
= markdown_field(label, :description)
|
||||
- elsif @project
|
||||
= render 'shared/label_full_path', label: label
|
||||
%ul.label-links.gl-m-0.gl-p-0.gl-white-space-nowrap
|
||||
- if show_label_issues_link
|
||||
%li.inline
|
||||
= link_to_label(label, css_class: 'gl-text-blue-600!') { _('Issues') }
|
||||
- if show_label_merge_requests_link
|
||||
·
|
||||
%li.inline
|
||||
= link_to_label(label, type: :merge_request, css_class: 'gl-text-blue-600!') { _('Merge requests') }
|
||||
= render_if_exists 'shared/label_row_epics_link', label: label
|
||||
- if force_priority
|
||||
·
|
||||
%li.js-priority-badge.inline.gl-ml-3
|
||||
.label-badge.gl-bg-blue-50= _('Prioritized label')
|
||||
- if @project && label.description.present?
|
||||
.gl-mt-3
|
||||
= render 'shared/label_full_path', label: label
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
- return unless instance_review_permitted?
|
||||
|
||||
%li.divider
|
||||
%li
|
||||
= link_to admin_instance_review_path, target: '_blank', class: 'text-nowrap' do
|
||||
= _("Get a free instance review")
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Added UsageData metrics for issues added/removed from Epics
|
||||
merge_request: 44371
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show labels origin path on project labels page
|
||||
merge_request: 43858
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Back-port free instance review for instances with 50+ users from EE Core to
|
||||
CE
|
||||
merge_request: 44770
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove Sentry implementation to investigate performance impact
|
||||
merge_request: 44643
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update the copy in the insert image modal to align with copy guidelines
|
||||
merge_request: 44949
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove the commit count from the commits API
|
||||
merge_request: 44934
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Incident Sla timer columns to DB
|
||||
merge_request: 44099
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix 500 error in block user API for internal user
|
||||
merge_request: 43461
|
||||
author: Sashi Kumar
|
||||
type: fixed
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: expose_environment_path_in_alert_details
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43414
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258638
|
||||
type: development
|
||||
group: group::progressive delivery
|
||||
default_enabled: false
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: api_commits_without_count
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43159
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254994
|
||||
name: incident_sla_dev
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/266931
|
||||
group: group::health
|
||||
type: development
|
||||
group: team::Scalability
|
||||
default_enabled: false
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: incident_sla
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648
|
||||
rollout_issue_url:
|
||||
group: group::health
|
||||
type: licensed
|
||||
default_enabled: true
|
|
@ -81,6 +81,8 @@ namespace :admin do
|
|||
post :preview, on: :collection
|
||||
end
|
||||
|
||||
get :instance_review, to: 'instance_review#index'
|
||||
|
||||
resource :health_check, controller: 'health_check', only: [:show]
|
||||
resource :background_jobs, controller: 'background_jobs', only: [:show]
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ function generateEntries() {
|
|||
|
||||
const manualEntries = {
|
||||
default: defaultEntries,
|
||||
sentry: './sentry/index.js',
|
||||
// sentry: './sentry/index.js', Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179
|
||||
performance_bar: './performance_bar/index.js',
|
||||
chrome_84_icon_fix: './lib/chrome_84_icon_fix.js',
|
||||
};
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSlaMinutesToProjectIncidentManagementSettings < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :project_incident_management_settings, :sla_timer, :boolean, default: false
|
||||
add_column :project_incident_management_settings, :sla_timer_minutes, :integer
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
77fa26f97216c1fa3d0b046dfabac92a5afa2a0eaf33439117b29ac81e740e4a
|
|
@ -14800,6 +14800,8 @@ CREATE TABLE project_incident_management_settings (
|
|||
encrypted_pagerduty_token bytea,
|
||||
encrypted_pagerduty_token_iv bytea,
|
||||
auto_close_incident boolean DEFAULT true NOT NULL,
|
||||
sla_timer boolean DEFAULT false,
|
||||
sla_timer_minutes integer,
|
||||
CONSTRAINT pagerduty_token_iv_length_constraint CHECK ((octet_length(encrypted_pagerduty_token_iv) <= 12)),
|
||||
CONSTRAINT pagerduty_token_length_constraint CHECK ((octet_length(encrypted_pagerduty_token) <= 255))
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/6995) in [GitLab Core](https://about.gitlab.com/pricing/) 11.3.
|
||||
|
||||
If you are running a medium size instance of GitLab Core edition you are qualified for a free Instance Review. You can find the button in the User menu.
|
||||
If you are running a medium size instance (50+ users) of GitLab Core edition you are qualified for a free Instance Review. You can find the button in the User menu.
|
||||
|
||||
![Instance Review button](img/instance_review_button.png)
|
||||
|
||||
|
|
|
@ -244,6 +244,11 @@ type AlertManagementAlert implements Noteable {
|
|||
"""
|
||||
endedAt: Time
|
||||
|
||||
"""
|
||||
Environment for the alert
|
||||
"""
|
||||
environment: Environment
|
||||
|
||||
"""
|
||||
Number of events of this alert
|
||||
"""
|
||||
|
@ -5773,7 +5778,7 @@ type Environment {
|
|||
id: ID!
|
||||
|
||||
"""
|
||||
The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.
|
||||
The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned
|
||||
"""
|
||||
latestOpenedMostSevereAlert: AlertManagementAlert
|
||||
|
||||
|
@ -5792,6 +5797,12 @@ type Environment {
|
|||
"""
|
||||
name: String!
|
||||
|
||||
"""
|
||||
The path to the environment. Will always return null if
|
||||
`expose_environment_path_in_alert_details` feature flag is disabled
|
||||
"""
|
||||
path: String
|
||||
|
||||
"""
|
||||
State of the environment, for example: available/stopped
|
||||
"""
|
||||
|
|
|
@ -666,6 +666,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "environment",
|
||||
"description": "Environment for the alert",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Environment",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "eventCount",
|
||||
"description": "Number of events of this alert",
|
||||
|
@ -15990,7 +16004,7 @@
|
|||
},
|
||||
{
|
||||
"name": "latestOpenedMostSevereAlert",
|
||||
"description": "The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.",
|
||||
"description": "The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -16047,6 +16061,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"description": "The path to the environment. Will always return null if `expose_environment_path_in_alert_details` feature flag is disabled",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the environment, for example: available/stopped",
|
||||
|
|
|
@ -77,6 +77,7 @@ Describes an alert from the project's Alert Management.
|
|||
| `details` | JSON | Alert details |
|
||||
| `detailsUrl` | String! | The URL of the alert detail page |
|
||||
| `endedAt` | Time | Timestamp the alert ended |
|
||||
| `environment` | Environment | Environment for the alert |
|
||||
| `eventCount` | Int | Number of events of this alert |
|
||||
| `hosts` | String! => Array | List of hosts the alert came from |
|
||||
| `iid` | ID! | Internal ID of the alert |
|
||||
|
@ -943,9 +944,10 @@ Describes where code is deployed for a project.
|
|||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `id` | ID! | ID of the environment |
|
||||
| `latestOpenedMostSevereAlert` | AlertManagementAlert | The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned. |
|
||||
| `latestOpenedMostSevereAlert` | AlertManagementAlert | The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned |
|
||||
| `metricsDashboard` | MetricsDashboard | Metrics dashboard schema for the environment |
|
||||
| `name` | String! | Human-readable name of the environment |
|
||||
| `path` | String | The path to the environment. Will always return null if `expose_environment_path_in_alert_details` feature flag is disabled |
|
||||
| `state` | String! | State of the environment, for example: available/stopped |
|
||||
|
||||
### Epic
|
||||
|
|
|
@ -1210,7 +1210,9 @@ Returns:
|
|||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to block an already blocked user by LDAP synchronization.
|
||||
- `403 Forbidden` when trying to block:
|
||||
- A user that is blocked through LDAP.
|
||||
- An internal user.
|
||||
|
||||
## Unblock user
|
||||
|
||||
|
|
|
@ -2819,6 +2819,8 @@ cache:
|
|||
- binaries/
|
||||
```
|
||||
|
||||
You can specify a [fallback cache key](#fallback-cache-key) to use if the specified `cache:key` is not found.
|
||||
|
||||
##### `cache:key:files`
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18986) in GitLab v12.5.
|
||||
|
@ -4404,6 +4406,32 @@ variables:
|
|||
|
||||
You can set them globally or per-job in the [`variables`](#variables) section.
|
||||
|
||||
### Fallback cache key
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1534) in GitLab Runner 13.4.
|
||||
|
||||
You can use the `$CI_COMMIT_REF_SLUG` variable to specify your [`cache:key`](#cachekey).
|
||||
For example, if your `$CI_COMMIT_REF_SLUG` is `test` you can set a job
|
||||
to download cache that's tagged with `test`.
|
||||
|
||||
If a cache with this tag is not found, you can use `CACHE_FALLBACK_KEY` to
|
||||
specify a cache to use when none exists.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
CACHE_FALLBACK_KEY: fallback-key
|
||||
|
||||
cache:
|
||||
key: "$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
- binaries/
|
||||
```
|
||||
|
||||
In this example, if the `$CI_COMMIT_REF_SLUG` is not found, the job uses the key defined
|
||||
by the `CACHE_FALLBACK_KEY` variable.
|
||||
|
||||
### Shallow cloning
|
||||
|
||||
> Introduced in GitLab 8.9 as an experimental feature.
|
||||
|
|
|
@ -308,3 +308,49 @@ Viewing logs from a metrics panel can be useful if you're triaging an
|
|||
application incident and need to [explore logs](../metrics/dashboards/index.md#chart-context-menu)
|
||||
from across your application. These logs help you understand what's affecting
|
||||
your application's performance and how to resolve any problems.
|
||||
|
||||
## View the environment that generated the alert
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232492) in GitLab 13.5.
|
||||
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-environment-link-in-alert-details). **(CORE ONLY)**
|
||||
|
||||
CAUTION: **Warning:**
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
||||
The environment information and the link are displayed in the Alert Details tab[#alert-details-tab].
|
||||
|
||||
### Enable or disable Environment Link in Alert Details **(CORE ONLY)**
|
||||
|
||||
Viewing the environment is under development and not ready for production use. It is
|
||||
deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:expose_environment_path_in_alert_details)
|
||||
```
|
||||
|
||||
To enable for just a particular project:
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('your-group/your-project')
|
||||
Feature.enable(:expose_environment_path_in_alert_details, project)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:expose_environment_path_in_alert_details)
|
||||
```
|
||||
|
||||
To disable for just a particular project:
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('your-group/your-project')
|
||||
Feature.disable(:expose_environment_path_in_alert_details, project)
|
||||
```
|
||||
|
|
|
@ -52,8 +52,9 @@ and edit labels.
|
|||
|
||||
View the project labels list by going to the project and clicking **Issues > Labels**.
|
||||
The list includes all labels that are defined at the project level, as well as all
|
||||
labels inherited from the immediate parent group. You can filter the list by entering a search
|
||||
query at the top and clicking search (**{search}**).
|
||||
labels inherited from the immediate parent group.
|
||||
For each label, you can see the project or group path from where it was created.
|
||||
You can filter the list by entering a search query at the top and clicking search (**{search}**).
|
||||
|
||||
To create a new project label:
|
||||
|
||||
|
|
|
@ -64,27 +64,13 @@ module API
|
|||
|
||||
serializer = with_stats ? Entities::CommitWithStats : Entities::Commit
|
||||
|
||||
if Feature.enabled?(:api_commits_without_count, user_project)
|
||||
# This tells kaminari that there is 1 more commit after the one we've
|
||||
# loaded, meaning there will be a next page, if the currently loaded set
|
||||
# of commits is equal to the requested page size.
|
||||
commit_count = offset + commits.size + 1
|
||||
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
|
||||
# This tells kaminari that there is 1 more commit after the one we've
|
||||
# loaded, meaning there will be a next page, if the currently loaded set
|
||||
# of commits is equal to the requested page size.
|
||||
commit_count = offset + commits.size + 1
|
||||
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
|
||||
|
||||
present paginate(paginated_commits, exclude_total_headers: true), with: serializer
|
||||
else
|
||||
commit_count =
|
||||
if all || path || before || after || first_parent
|
||||
user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent)
|
||||
else
|
||||
# Cacheable commit count.
|
||||
user_project.repository.commit_count_for_ref(ref)
|
||||
end
|
||||
|
||||
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
|
||||
|
||||
present paginate(paginated_commits), with: serializer
|
||||
end
|
||||
present paginate(paginated_commits, exclude_total_headers: true), with: serializer
|
||||
end
|
||||
|
||||
desc 'Commit multiple file changes as one commit' do
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SubscriptionPortal
|
||||
def self.default_subscriptions_url
|
||||
::Gitlab.dev_or_test_env? ? 'https://customers.stg.gitlab.com' : 'https://customers.gitlab.com'
|
||||
end
|
||||
|
||||
SUBSCRIPTIONS_URL = ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url).freeze
|
||||
end
|
||||
end
|
|
@ -24,6 +24,9 @@ module Gitlab
|
|||
ISSUE_MARKED_AS_DUPLICATE = 'g_project_management_issue_marked_as_duplicate'
|
||||
ISSUE_LOCKED = 'g_project_management_issue_locked'
|
||||
ISSUE_UNLOCKED = 'g_project_management_issue_unlocked'
|
||||
ISSUE_ADDED_TO_EPIC = 'g_project_management_issue_added_to_epic'
|
||||
ISSUE_REMOVED_FROM_EPIC = 'g_project_management_issue_removed_from_epic'
|
||||
ISSUE_CHANGED_EPIC = 'g_project_management_issue_changed_epic'
|
||||
|
||||
class << self
|
||||
def track_issue_created_action(author:, time: Time.zone.now)
|
||||
|
@ -102,6 +105,18 @@ module Gitlab
|
|||
track_unique_action(ISSUE_UNLOCKED, author, time)
|
||||
end
|
||||
|
||||
def track_issue_added_to_epic_action(author:, time: Time.zone.now)
|
||||
track_unique_action(ISSUE_ADDED_TO_EPIC, author, time)
|
||||
end
|
||||
|
||||
def track_issue_removed_from_epic_action(author:, time: Time.zone.now)
|
||||
track_unique_action(ISSUE_REMOVED_FROM_EPIC, author, time)
|
||||
end
|
||||
|
||||
def track_issue_changed_epic_action(author:, time: Time.zone.now)
|
||||
track_unique_action(ISSUE_CHANGED_EPIC, author, time)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_unique_action(action, author, time)
|
||||
|
|
|
@ -267,3 +267,15 @@
|
|||
category: issues_edit
|
||||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
- name: g_project_management_issue_added_to_epic
|
||||
category: issues_edit
|
||||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
- name: g_project_management_issue_removed_from_epic
|
||||
category: issues_edit
|
||||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
- name: g_project_management_issue_changed_epic
|
||||
category: issues_edit
|
||||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
|
|
|
@ -13451,10 +13451,10 @@ msgstr ""
|
|||
msgid "Ignored"
|
||||
msgstr ""
|
||||
|
||||
msgid "Image Details"
|
||||
msgid "Image URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Image URL"
|
||||
msgid "Image details"
|
||||
msgstr ""
|
||||
|
||||
msgid "ImageDiffViewer|2-up"
|
||||
|
@ -13728,12 +13728,18 @@ msgstr ""
|
|||
msgid "IncidentManagement|Unpublished"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Activate \"time to SLA\" countdown timer"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Alert integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Grafana integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Incident settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Incidents"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13743,6 +13749,30 @@ msgstr ""
|
|||
msgid "IncidentSettings|Set up integrations with external tools to help better manage incidents."
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Time limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Time limit must be a multiple of 15 minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Time limit must be a valid number"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|Time limit must be greater than 0"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|When activated, this will apply to all new incidents within the project"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|You may choose to introduce a countdown timer in incident issues to better track Service Level Agreements (SLAs). The timer is automatically started when the incident is created, and sets a time limit for the incident to be resolved in. When activated, \"time to SLA\" countdown will appear on all new incidents."
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentSettings|minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Incidents"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13830,9 +13860,6 @@ msgstr ""
|
|||
msgid "Input your repository URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert a code block"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13845,6 +13872,9 @@ msgstr ""
|
|||
msgid "Insert code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert inline code"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-3",
|
||||
"@rails/ujs": "^6.0.3-2",
|
||||
"@sentry/browser": "^5.22.3",
|
||||
"@sourcegraph/code-host-integration": "0.0.50",
|
||||
"@toast-ui/editor": "^2.4.0",
|
||||
"@toast-ui/vue-editor": "^2.4.0",
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Admin::InstanceReviewController do
|
||||
include UsageDataHelpers
|
||||
|
||||
let(:admin) { create(:admin) }
|
||||
let(:subscriptions_url) { ::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL }
|
||||
|
||||
before do
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
context 'GET #index' do
|
||||
let!(:group) { create(:group) }
|
||||
let!(:projects) { create_list(:project, 2, group: group) }
|
||||
|
||||
subject { post :index }
|
||||
|
||||
context 'with usage ping enabled' do
|
||||
before do
|
||||
stub_application_setting(usage_ping_enabled: true)
|
||||
stub_usage_data_connections
|
||||
::Gitlab::UsageData.data(force_refresh: true)
|
||||
subject
|
||||
end
|
||||
|
||||
it 'redirects to the customers app with correct params' do
|
||||
params = { instance_review: {
|
||||
email: admin.email,
|
||||
last_name: admin.name,
|
||||
version: ::Gitlab::VERSION,
|
||||
users_count: 5,
|
||||
projects_count: 2,
|
||||
groups_count: 1,
|
||||
issues_count: 0,
|
||||
merge_requests_count: 0,
|
||||
internal_pipelines_count: 0,
|
||||
external_pipelines_count: 0,
|
||||
labels_count: 0,
|
||||
milestones_count: 0,
|
||||
snippets_count: 0,
|
||||
notes_count: 0
|
||||
} }.to_query
|
||||
|
||||
expect(response).to redirect_to("#{subscriptions_url}/instance_review?#{params}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usage ping disabled' do
|
||||
before do
|
||||
stub_application_setting(usage_ping_enabled: false)
|
||||
subject
|
||||
end
|
||||
|
||||
it 'redirects to the customers app with correct params' do
|
||||
params = { instance_review: {
|
||||
email: admin.email,
|
||||
last_name: admin.name,
|
||||
version: ::Gitlab::VERSION
|
||||
} }.to_query
|
||||
|
||||
expect(response).to redirect_to("#{subscriptions_url}/instance_review?#{params}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -42,7 +42,7 @@ RSpec.describe Projects::AlertManagementController do
|
|||
let(:role) { :reporter }
|
||||
|
||||
it 'shows 404' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
get :details, params: { namespace_id: project.namespace, project_id: project, id: id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Alert management', :js do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
|
||||
before_all do
|
||||
project.add_developer(developer)
|
||||
end
|
||||
|
||||
context 'when visiting the alert details page' do
|
||||
let!(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, title: 'dos-test', project: project, **options) }
|
||||
let(:options) { {} }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when actor has permission to see the alert' do
|
||||
let(:user) { developer }
|
||||
|
||||
it 'shows the alert details' do
|
||||
visit(details_project_alert_management_path(project, alert))
|
||||
|
||||
within('.alert-management-details-table') do
|
||||
expect(page).to have_content(alert.title)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when alert belongs to an environment' do
|
||||
let(:options) { { environment: environment } }
|
||||
let!(:environment) { create(:environment, name: 'production', project: project) }
|
||||
|
||||
it 'shows the environment name' do
|
||||
visit(details_project_alert_management_path(project, alert))
|
||||
|
||||
expect(page).to have_link(environment.name, href: project_environment_path(project, environment))
|
||||
within('.alert-management-details-table') do
|
||||
expect(page).to have_content(environment.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when expose_environment_path_in_alert_details feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(expose_environment_path_in_alert_details: false)
|
||||
end
|
||||
|
||||
it 'does not show the environment name' do
|
||||
visit(details_project_alert_management_path(project, alert))
|
||||
|
||||
within('.alert-management-details-table') do
|
||||
expect(page).to have_content(alert.title)
|
||||
expect(page).not_to have_content(environment.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,7 +12,7 @@ RSpec.describe 'Sentry' do
|
|||
expect(has_requested_sentry).to eq(false)
|
||||
end
|
||||
|
||||
it 'loads sentry if sentry is enabled' do
|
||||
xit 'loads sentry if sentry is enabled' do
|
||||
stub_sentry_settings
|
||||
|
||||
visit new_user_session_path
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import AlertDetails from '~/alert_management/components/alert_details.vue';
|
||||
import AlertSummaryRow from '~/alert_management/components/alert_summary_row.vue';
|
||||
import {
|
||||
ALERTS_SEVERITY_LABELS,
|
||||
trackAlertsDetailsViewsOptions,
|
||||
} from '~/alert_management/constants';
|
||||
import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
trackAlertsDetailsViewsOptions,
|
||||
ALERTS_SEVERITY_LABELS,
|
||||
} from '~/alert_management/constants';
|
||||
import Tracking from '~/tracking';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import mockAlerts from '../mocks/alerts.json';
|
||||
|
||||
const mockAlert = mockAlerts[0];
|
||||
const environmentName = 'Production';
|
||||
const environmentPath = '/fake/path';
|
||||
|
||||
describe('AlertDetails', () => {
|
||||
let wrapper;
|
||||
let environmentData = {
|
||||
name: environmentName,
|
||||
path: environmentPath,
|
||||
};
|
||||
let glFeatures = { exposeEnvironmentPathInAlertDetails: false };
|
||||
let mock;
|
||||
let wrapper;
|
||||
const projectPath = 'root/alerts';
|
||||
const projectIssuesPath = 'root/alerts/-/issues';
|
||||
const projectId = '1';
|
||||
|
@ -33,9 +40,17 @@ describe('AlertDetails', () => {
|
|||
projectPath,
|
||||
projectIssuesPath,
|
||||
projectId,
|
||||
glFeatures,
|
||||
},
|
||||
data() {
|
||||
return { alert: { ...mockAlert }, sidebarStatus: false, ...data };
|
||||
return {
|
||||
alert: {
|
||||
...mockAlert,
|
||||
environment: environmentData,
|
||||
},
|
||||
sidebarStatus: false,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
|
@ -72,7 +87,8 @@ describe('AlertDetails', () => {
|
|||
const findCreateIncidentBtn = () => wrapper.findByTestId('createIncidentBtn');
|
||||
const findViewIncidentBtn = () => wrapper.findByTestId('viewIncidentBtn');
|
||||
const findIncidentCreationAlert = () => wrapper.findByTestId('incidentCreationError');
|
||||
const findEnvironmentLink = () => wrapper.findByTestId('environmentUrl');
|
||||
const findEnvironmentName = () => wrapper.findByTestId('environmentName');
|
||||
const findEnvironmentPath = () => wrapper.findByTestId('environmentPath');
|
||||
const findDetailsTable = () => wrapper.find(AlertDetailsTable);
|
||||
|
||||
describe('Alert details', () => {
|
||||
|
@ -120,8 +136,6 @@ describe('AlertDetails', () => {
|
|||
field | data | isShown
|
||||
${'eventCount'} | ${1} | ${true}
|
||||
${'eventCount'} | ${undefined} | ${false}
|
||||
${'environment'} | ${undefined} | ${false}
|
||||
${'environment'} | ${'Production'} | ${true}
|
||||
${'monitoringTool'} | ${'New Relic'} | ${true}
|
||||
${'monitoringTool'} | ${undefined} | ${false}
|
||||
${'service'} | ${'Prometheus'} | ${true}
|
||||
|
@ -144,16 +158,34 @@ describe('AlertDetails', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('environment URL fields', () => {
|
||||
it('should show the environment URL when available', () => {
|
||||
const environment = 'Production';
|
||||
const environmentUrl = 'fake/url';
|
||||
mountComponent({
|
||||
data: { alert: { ...mockAlert, environment, environmentUrl } },
|
||||
describe('environment fields', () => {
|
||||
describe('when exposeEnvironmentPathInAlertDetails is disabled', () => {
|
||||
beforeEach(mountComponent);
|
||||
|
||||
it('should not show the environment', () => {
|
||||
expect(findEnvironmentName().exists()).toBe(false);
|
||||
expect(findEnvironmentPath().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when exposeEnvironmentPathInAlertDetails is enabled', () => {
|
||||
beforeEach(() => {
|
||||
glFeatures = { exposeEnvironmentPathInAlertDetails: true };
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
expect(findEnvironmentLink().text()).toBe(environment);
|
||||
expect(findEnvironmentLink().attributes('href')).toBe(environmentUrl);
|
||||
it('should show the environment name with link to path', () => {
|
||||
expect(findEnvironmentName().exists()).toBe(false);
|
||||
expect(findEnvironmentPath().text()).toBe(environmentName);
|
||||
expect(findEnvironmentPath().attributes('href')).toBe(environmentPath);
|
||||
});
|
||||
|
||||
it('should only show the environment name if the path is not provided', () => {
|
||||
environmentData = { name: environmentName, path: null };
|
||||
mountComponent();
|
||||
expect(findEnvironmentPath().exists()).toBe(false);
|
||||
expect(findEnvironmentName().text()).toBe(environmentName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
|
||||
GlTable,
|
||||
} from '@gitlab/ui';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import Clusters from '~/clusters_list/components/clusters.vue';
|
||||
import ClusterStore from '~/clusters_list/store';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import { deprecatedCreateFlash as flashError } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
|
@ -58,6 +58,8 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
|
|||
/>
|
||||
</gl-tab-stub>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
</gl-tabs-stub>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -6,7 +6,12 @@ describe('IncidentsSettingTabs', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(IncidentsSettingTabs);
|
||||
wrapper = shallowMount(IncidentsSettingTabs, {
|
||||
provide: {
|
||||
service: {},
|
||||
serviceLevelAgreementSettings: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import * as Sentry from '~/sentry/wrapper';
|
||||
import SentryConfig from '~/sentry/sentry_config';
|
||||
|
||||
describe('SentryConfig', () => {
|
||||
|
|
|
@ -18,13 +18,24 @@ const mockAlert = {
|
|||
__typename: 'AlertManagementAlert',
|
||||
};
|
||||
|
||||
const environmentName = 'Production';
|
||||
const environmentPath = '/fake/path';
|
||||
|
||||
describe('AlertDetails', () => {
|
||||
let environmentData = { name: environmentName, path: environmentPath };
|
||||
let glFeatures = { exposeEnvironmentPathInAlertDetails: false };
|
||||
let wrapper;
|
||||
|
||||
function mountComponent(propsData = {}) {
|
||||
wrapper = mount(AlertDetailsTable, {
|
||||
provide: {
|
||||
glFeatures,
|
||||
},
|
||||
propsData: {
|
||||
alert: mockAlert,
|
||||
alert: {
|
||||
...mockAlert,
|
||||
environment: environmentData,
|
||||
},
|
||||
loading: false,
|
||||
...propsData,
|
||||
},
|
||||
|
@ -38,6 +49,12 @@ describe('AlertDetails', () => {
|
|||
|
||||
const findTableComponent = () => wrapper.find(GlTable);
|
||||
const findTableKeys = () => findTableComponent().findAll('tbody td:first-child');
|
||||
const findTableFieldValueByKey = fieldKey =>
|
||||
findTableComponent()
|
||||
.findAll('tbody tr')
|
||||
.filter(row => row.text().includes(fieldKey))
|
||||
.at(0)
|
||||
.find('td:nth-child(2)');
|
||||
const findTableField = (fields, fieldName) => fields.filter(row => row.text() === fieldName);
|
||||
|
||||
describe('Alert details', () => {
|
||||
|
@ -62,11 +79,7 @@ describe('AlertDetails', () => {
|
|||
});
|
||||
|
||||
describe('with table data', () => {
|
||||
const environment = 'myEnvironment';
|
||||
const environmentUrl = 'fake/url';
|
||||
beforeEach(() => {
|
||||
mountComponent({ alert: { ...mockAlert, environment, environmentUrl } });
|
||||
});
|
||||
beforeEach(mountComponent);
|
||||
|
||||
it('renders a table', () => {
|
||||
expect(findTableComponent().exists()).toBe(true);
|
||||
|
@ -83,18 +96,43 @@ describe('AlertDetails', () => {
|
|||
expect(findTableField(fields, 'Title').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Severity').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Status').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Environment').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Hosts').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Environment').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not show disallowed alert fields', () => {
|
||||
it('should not show disallowed and flaggedAllowed alert fields', () => {
|
||||
const fields = findTableKeys();
|
||||
|
||||
expect(findTableField(fields, 'Typename').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Todos').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Notes').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Assignees').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'EnvironmentUrl').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Environment').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when exposeEnvironmentPathInAlertDetails is enabled', () => {
|
||||
beforeEach(() => {
|
||||
glFeatures = { exposeEnvironmentPathInAlertDetails: true };
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
it('should show flaggedAllowed alert fields', () => {
|
||||
const fields = findTableKeys();
|
||||
|
||||
expect(findTableField(fields, 'Environment').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display only the name for the environment', () => {
|
||||
expect(findTableFieldValueByKey('Iid').text()).toBe('1527542');
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBe(environmentName);
|
||||
});
|
||||
|
||||
it('should not display the environment row if there is not data', () => {
|
||||
environmentData = { name: null, path: null };
|
||||
mountComponent();
|
||||
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
|
|||
todos
|
||||
details_url
|
||||
prometheus_alert
|
||||
environment
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Environment'] do
|
|||
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %w[
|
||||
name id state metrics_dashboard latest_opened_most_severe_alert
|
||||
name id state metrics_dashboard latest_opened_most_severe_alert path
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['Environment'] do
|
|||
project(fullPath: "#{project.full_path}") {
|
||||
environment(name: "#{environment.name}") {
|
||||
name
|
||||
path
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +44,18 @@ RSpec.describe GitlabSchema.types['Environment'] do
|
|||
expect(subject['data']['project']['environment']['name']).to eq(environment.name)
|
||||
end
|
||||
|
||||
it 'returns the path when the feature is enabled' do
|
||||
expect(subject['data']['project']['environment']['path']).to eq(
|
||||
Gitlab::Routing.url_helpers.project_environment_path(project, environment)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not return the path when the feature is disabled' do
|
||||
stub_feature_flags(expose_environment_path_in_alert_details: false)
|
||||
|
||||
expect(subject['data']['project']['environment']['path']).to be_nil
|
||||
end
|
||||
|
||||
context 'when query alert data for the environment' do
|
||||
let_it_be(:query) do
|
||||
%(
|
||||
|
|
|
@ -222,6 +222,32 @@ RSpec.describe ApplicationHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#instance_review_permitted?' do
|
||||
let_it_be(:non_admin_user) { create :user }
|
||||
let_it_be(:admin_user) { create :user, :admin }
|
||||
|
||||
before do
|
||||
allow(::Gitlab::CurrentSettings).to receive(:instance_review_permitted?).and_return(app_setting)
|
||||
allow(helper).to receive(:current_user).and_return(current_user)
|
||||
end
|
||||
|
||||
subject { helper.instance_review_permitted? }
|
||||
|
||||
where(app_setting: [true, false], is_admin: [true, false, nil])
|
||||
|
||||
with_them do
|
||||
let(:current_user) do
|
||||
if is_admin.nil?
|
||||
nil
|
||||
else
|
||||
is_admin ? admin_user : non_admin_user
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to be(app_setting && is_admin) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#locale_path' do
|
||||
it 'returns the locale path with an `_`' do
|
||||
Gitlab::I18n.with_locale('pt-BR') do
|
||||
|
|
|
@ -145,7 +145,7 @@ RSpec.describe OperationsHelper do
|
|||
subject { helper.operations_settings_data }
|
||||
|
||||
it 'returns the correct set of data' do
|
||||
is_expected.to eq(
|
||||
is_expected.to include(
|
||||
operations_settings_endpoint: project_settings_operations_path(project),
|
||||
templates: '[]',
|
||||
create_issue: 'false',
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::Gitlab::SubscriptionPortal do
|
||||
describe '.default_subscriptions_url' do
|
||||
subject { described_class.default_subscriptions_url }
|
||||
|
||||
context 'on non test and non dev environments' do
|
||||
before do
|
||||
allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
|
||||
allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns production subscriptions app URL' do
|
||||
is_expected.to eq('https://customers.gitlab.com')
|
||||
end
|
||||
end
|
||||
|
||||
context 'on dev environment' do
|
||||
before do
|
||||
allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
|
||||
allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns staging subscriptions app url' do
|
||||
is_expected.to eq('https://customers.stg.gitlab.com')
|
||||
end
|
||||
end
|
||||
|
||||
context 'on test environment' do
|
||||
before do
|
||||
allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
|
||||
allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns staging subscriptions app url' do
|
||||
is_expected.to eq('https://customers.stg.gitlab.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -202,6 +202,36 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
|
|||
end
|
||||
end
|
||||
|
||||
context 'for Issue added to epic actions' do
|
||||
it_behaves_like 'tracks and counts action' do
|
||||
let(:action) { described_class::ISSUE_ADDED_TO_EPIC}
|
||||
|
||||
def track_action(params)
|
||||
described_class.track_issue_added_to_epic_action(**params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for Issue removed from epic actions' do
|
||||
it_behaves_like 'tracks and counts action' do
|
||||
let(:action) { described_class::ISSUE_REMOVED_FROM_EPIC}
|
||||
|
||||
def track_action(params)
|
||||
described_class.track_issue_removed_from_epic_action(**params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for Issue changed epic actions' do
|
||||
it_behaves_like 'tracks and counts action' do
|
||||
let(:action) { described_class::ISSUE_CHANGED_EPIC}
|
||||
|
||||
def track_action(params)
|
||||
described_class.track_issue_changed_epic_action(**params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'can return the count of actions per user deduplicated', :aggregate_failures do
|
||||
described_class.track_issue_title_changed_action(author: user1)
|
||||
described_class.track_issue_description_changed_action(author: user1)
|
||||
|
|
|
@ -807,6 +807,23 @@ RSpec.describe ApplicationSetting do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#instance_review_permitted?', :request_store do
|
||||
subject { setting.instance_review_permitted? }
|
||||
|
||||
before do
|
||||
RequestStore.store[:current_license] = nil
|
||||
expect(Rails.cache).to receive(:fetch).and_return(
|
||||
::ApplicationSetting::INSTANCE_REVIEW_MIN_USERS + users_over_minimum
|
||||
)
|
||||
end
|
||||
|
||||
where(users_over_minimum: [-1, 0, 1])
|
||||
|
||||
with_them do
|
||||
it { is_expected.to be(users_over_minimum >= 0) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'email_restrictions' do
|
||||
context 'when email restrictions are enabled' do
|
||||
before do
|
||||
|
|
|
@ -91,4 +91,18 @@ RSpec.describe LabelPresenter do
|
|||
it { is_expected.to eq(label.project.name) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#subject_full_name' do
|
||||
context 'with group label' do
|
||||
subject { group_label.subject_full_name }
|
||||
|
||||
it { is_expected.to eq(group_label.group.full_name) }
|
||||
end
|
||||
|
||||
context 'with project label' do
|
||||
subject { label.subject_full_name }
|
||||
|
||||
it { is_expected.to eq(label.project.full_name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,17 +40,6 @@ RSpec.describe API::Commits do
|
|||
|
||||
expect(response).to include_limited_pagination_headers
|
||||
end
|
||||
|
||||
it 'includes the total headers when the count is not disabled' do
|
||||
stub_feature_flags(api_commits_without_count: false)
|
||||
commit_count = project.repository.count_commits(ref: 'master').to_s
|
||||
|
||||
get api(route, current_user)
|
||||
|
||||
expect(response).to include_pagination_headers
|
||||
expect(response.headers['X-Total']).to eq(commit_count)
|
||||
expect(response.headers['X-Page']).to eql('1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
|
|
|
@ -2524,6 +2524,15 @@ RSpec.describe API::Users, :do_not_mock_admin_mode do
|
|||
expect(json_response['message']).to eq('404 User Not Found')
|
||||
end
|
||||
|
||||
it 'returns a 403 error if user is internal' do
|
||||
internal_user = create(:user, :bot)
|
||||
|
||||
post api("/users/#{internal_user.id}/block", admin)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(json_response['message']).to eq('An internal user cannot be blocked')
|
||||
end
|
||||
|
||||
it 'returns a 201 if user is already blocked' do
|
||||
post api("/users/#{blocked_user.id}/block", admin)
|
||||
|
||||
|
|
|
@ -3,18 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Update of user activity' do
|
||||
let(:user) { create(:user, last_activity_on: nil) }
|
||||
|
||||
before do
|
||||
group = create(:group, name: 'group')
|
||||
project = create(:project, :public, namespace: group, name: 'project')
|
||||
|
||||
create(:issue, project: project, iid: 10)
|
||||
create(:merge_request, source_project: project, iid: 15)
|
||||
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
paths_to_visit = [
|
||||
'/group',
|
||||
'/group/project',
|
||||
|
@ -30,85 +18,5 @@ RSpec.describe 'Update of user activity' do
|
|||
'/group/project/-/merge_requests/15'
|
||||
]
|
||||
|
||||
context 'without an authenticated user' do
|
||||
it 'does not set the last activity cookie' do
|
||||
get "/group/project"
|
||||
|
||||
expect(response.cookies['user_last_activity_on']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an authenticated user' do
|
||||
before do
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
context 'with a POST request' do
|
||||
it 'does not set the last activity cookie' do
|
||||
post "/group/project/archive"
|
||||
|
||||
expect(response.cookies['user_last_activity_on']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
paths_to_visit.each do |path|
|
||||
context "on GET to #{path}" do
|
||||
it 'updates the last activity date' do
|
||||
expect(Users::ActivityService).to receive(:new).and_call_original
|
||||
|
||||
get path
|
||||
|
||||
expect(user.last_activity_on).to eq(Date.today)
|
||||
end
|
||||
|
||||
context 'when calling it twice' do
|
||||
it 'updates last_activity_on just once' do
|
||||
expect(Users::ActivityService).to receive(:new).once.and_call_original
|
||||
|
||||
2.times do
|
||||
get path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_activity_on is nil' do
|
||||
before do
|
||||
user.update_attribute(:last_activity_on, nil)
|
||||
end
|
||||
|
||||
it 'updates the last activity date' do
|
||||
expect(user.last_activity_on).to be_nil
|
||||
|
||||
get path
|
||||
|
||||
expect(user.last_activity_on).to eq(Date.today)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_activity_on is stale' do
|
||||
before do
|
||||
user.update_attribute(:last_activity_on, 2.days.ago.to_date)
|
||||
end
|
||||
|
||||
it 'updates the last activity date' do
|
||||
get path
|
||||
|
||||
expect(user.last_activity_on).to eq(Date.today)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_activity_on is up to date' do
|
||||
before do
|
||||
user.update_attribute(:last_activity_on, Date.today)
|
||||
end
|
||||
|
||||
it 'does not try to update it' do
|
||||
expect(Users::ActivityService).not_to receive(:new)
|
||||
|
||||
get path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
it_behaves_like 'updating of user activity', paths_to_visit
|
||||
end
|
||||
|
|
|
@ -34,5 +34,15 @@ RSpec.describe Users::BlockService do
|
|||
expect { operation }.not_to change { user.state }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when internal user' do
|
||||
let(:user) { create(:user, :bot) }
|
||||
|
||||
it 'returns error result' do
|
||||
expect(operation[:status]).to eq(:error)
|
||||
expect(operation[:message]).to eq('An internal user cannot be blocked')
|
||||
expect(operation[:http_status]).to eq(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'updating of user activity' do |paths_to_visit|
|
||||
let(:user) { create(:user, last_activity_on: nil) }
|
||||
|
||||
before do
|
||||
group = create(:group, name: 'group')
|
||||
project = create(:project, :public, namespace: group, name: 'project')
|
||||
|
||||
create(:issue, project: project, iid: 10)
|
||||
create(:merge_request, source_project: project, iid: 15)
|
||||
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'without an authenticated user' do
|
||||
it 'does not set the last activity cookie' do
|
||||
get "/group/project"
|
||||
|
||||
expect(response.cookies['user_last_activity_on']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an authenticated user' do
|
||||
before do
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
context 'with a POST request' do
|
||||
it 'does not set the last activity cookie' do
|
||||
post "/group/project/archive"
|
||||
|
||||
expect(response.cookies['user_last_activity_on']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
paths_to_visit.each do |path|
|
||||
context "on GET to #{path}" do
|
||||
it 'updates the last activity date' do
|
||||
expect(Users::ActivityService).to receive(:new).and_call_original
|
||||
|
||||
get path
|
||||
|
||||
expect(user.last_activity_on).to eq(Date.today)
|
||||
end
|
||||
|
||||
context 'when calling it twice' do
|
||||
it 'updates last_activity_on just once' do
|
||||
expect(Users::ActivityService).to receive(:new).once.and_call_original
|
||||
|
||||
2.times do
|
||||
get path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_activity_on is nil' do
|
||||
before do
|
||||
user.update_attribute(:last_activity_on, nil)
|
||||
end
|
||||
|
||||
it 'updates the last activity date' do
|
||||
expect(user.last_activity_on).to be_nil
|
||||
|
||||
get path
|
||||
|
||||
expect(user.last_activity_on).to eq(Date.today)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_activity_on is stale' do
|
||||
before do
|
||||
user.update_attribute(:last_activity_on, 2.days.ago.to_date)
|
||||
end
|
||||
|
||||
it 'updates the last activity date' do
|
||||
get path
|
||||
|
||||
expect(user.last_activity_on).to eq(Date.today)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_activity_on is up to date' do
|
||||
before do
|
||||
user.update_attribute(:last_activity_on, Date.today)
|
||||
end
|
||||
|
||||
it 'does not try to update it' do
|
||||
expect(Users::ActivityService).not_to receive(:new)
|
||||
|
||||
get path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,43 +2,83 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'shared/_label_row.html.haml' do
|
||||
label_types = {
|
||||
'project label': :label,
|
||||
'group label': :group_label
|
||||
}
|
||||
let_it_be(:group) { create(:group) }
|
||||
let(:label) { build_stubbed(:group_label, group: group).present(issuable_subject: group) }
|
||||
|
||||
label_types.each do |label_type, label_factory|
|
||||
let!(:label) do
|
||||
label_record = create(label_factory) # rubocop: disable Rails/SaveBang
|
||||
label_record.present(issuable_subject: label_record.subject)
|
||||
before do
|
||||
allow(view).to receive(:label) { label }
|
||||
end
|
||||
|
||||
context 'with a project context' do
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let(:label) { build_stubbed(:label, project: project).present(issuable_subject: project) }
|
||||
|
||||
before do
|
||||
assign(:project, label.project)
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
context "for a #{label_type}" do
|
||||
before do
|
||||
if label.project_label?
|
||||
@project = label.project
|
||||
else
|
||||
@group = label.group
|
||||
end
|
||||
end
|
||||
it 'has a non-linked label title' do
|
||||
expect(rendered).not_to have_css('a', text: label.title)
|
||||
end
|
||||
|
||||
it 'has a non-linked label title' do
|
||||
render 'shared/label_row', label: label
|
||||
it "has Issues link" do
|
||||
expect(rendered).to have_css('a', text: 'Issues')
|
||||
end
|
||||
|
||||
expect(rendered).not_to have_css('a', text: label.title)
|
||||
end
|
||||
it "has Merge request link" do
|
||||
expect(rendered).to have_css('a', text: 'Merge requests')
|
||||
end
|
||||
|
||||
it "has Issues link for #{label_type}" do
|
||||
render 'shared/label_row', label: label
|
||||
it "shows the path from where the label was created" do
|
||||
expect(rendered).to have_css('.label-badge', text: project.full_name)
|
||||
end
|
||||
end
|
||||
|
||||
expect(rendered).to have_css('a', text: 'Issues')
|
||||
end
|
||||
context 'with a group context' do
|
||||
before do
|
||||
assign(:group, label.group)
|
||||
|
||||
it "has Merge request link for #{label_type}" do
|
||||
render 'shared/label_row', label: label
|
||||
render
|
||||
end
|
||||
|
||||
expect(rendered).to have_css('a', text: 'Merge requests')
|
||||
end
|
||||
it 'has a non-linked label title' do
|
||||
expect(rendered).not_to have_css('a', text: label.title)
|
||||
end
|
||||
|
||||
it "has Issues link" do
|
||||
expect(rendered).to have_css('a', text: 'Issues')
|
||||
end
|
||||
|
||||
it "has Merge request link" do
|
||||
expect(rendered).to have_css('a', text: 'Merge requests')
|
||||
end
|
||||
|
||||
it "does not show a path from where the label was created" do
|
||||
expect(rendered).not_to have_css('.label-badge')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an admin context' do
|
||||
before do
|
||||
render
|
||||
end
|
||||
|
||||
it 'has a non-linked label title' do
|
||||
expect(rendered).not_to have_css('a', text: label.title)
|
||||
end
|
||||
|
||||
it "does not show Issues link" do
|
||||
expect(rendered).not_to have_css('a', text: 'Issues')
|
||||
end
|
||||
|
||||
it "does not show Merge request link" do
|
||||
expect(rendered).not_to have_css('a', text: 'Merge requests')
|
||||
end
|
||||
|
||||
it "does not show a path from where the label was created" do
|
||||
expect(rendered).not_to have_css('.label-badge')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
52
yarn.lock
52
yarn.lock
|
@ -1129,58 +1129,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.0.3-2.tgz#e14c1f29086858215ce7ccd9ad6d8888c458b4a3"
|
||||
integrity sha512-WcpIEftNCfGDEgk6KerOugiet75Mir5q/HT1yt3dDhpBI91BaZ15lfSQIsZwMw2nyeDz9A9QBz8dAFAd4gXIzg==
|
||||
|
||||
"@sentry/browser@^5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.3.tgz#7a64bd1cf01bf393741a3e4bf35f82aa927f5b4e"
|
||||
integrity sha512-2TzE/CoBa5ZkvxJizDdi1Iz1ldmXSJpFQ1mL07PIXBjCt0Wxf+WOuFSj5IP4L40XHfJE5gU8wEvSH0VDR8nXtA==
|
||||
dependencies:
|
||||
"@sentry/core" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7"
|
||||
integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.22.3"
|
||||
"@sentry/minimal" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5"
|
||||
integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw==
|
||||
dependencies:
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440"
|
||||
integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18"
|
||||
integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw==
|
||||
|
||||
"@sentry/utils@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f"
|
||||
integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw==
|
||||
dependencies:
|
||||
"@sentry/types" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sindresorhus/is@^0.14.0":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||
|
|
Loading…
Reference in New Issue