Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-13 06:09:09 +00:00
parent f645d7e060
commit 8f71e69fdb
93 changed files with 1075 additions and 318 deletions

View File

@ -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"

View File

@ -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);

View File

@ -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);

View File

@ -11,6 +11,10 @@ fragment AlertDetailItem on AlertManagementAlert {
updatedAt
endedAt
hosts
environment {
name
path
}
details
runbook
todos {

View File

@ -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';

View File

@ -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';

View File

@ -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 = {

View File

@ -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>

View File

@ -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);

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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 = [

View File

@ -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;
},
});
}

View File

@ -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>

View File

@ -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'),

View File

@ -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;

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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!

View File

@ -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

View File

@ -51,3 +51,5 @@ module IncidentManagement
end
end
end
IncidentManagement::ProjectIncidentManagementSetting.prepend_if_ee('EE::IncidentManagement::ProjectIncidentManagementSetting')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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/"

View File

@ -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/"

View File

@ -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 } }

View File

@ -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

View File

@ -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
&middot;
%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
&middot;
%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
&middot;
%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
&middot;
%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

View File

@ -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")

View File

@ -0,0 +1,5 @@
---
title: Added UsageData metrics for issues added/removed from Epics
merge_request: 44371
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Show labels origin path on project labels page
merge_request: 43858
author:
type: added

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Remove Sentry implementation to investigate performance impact
merge_request: 44643
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Update the copy in the insert image modal to align with copy guidelines
merge_request: 44949
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Remove the commit count from the commits API
merge_request: 44934
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Add Incident Sla timer columns to DB
merge_request: 44099
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix 500 error in block user API for internal user
merge_request: 43461
author: Sashi Kumar
type: fixed

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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',
};

View File

@ -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

View File

@ -0,0 +1 @@
77fa26f97216c1fa3d0b046dfabac92a5afa2a0eaf33439117b29ac81e740e4a

View File

@ -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))
);

View File

@ -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)

View File

@ -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
"""

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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)
```

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
});
});
});

View File

@ -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';

View File

@ -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';

View File

@ -58,6 +58,8 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
/>
</gl-tab-stub>
<!---->
<!---->
</gl-tabs-stub>
</div>
</section>

View File

@ -6,7 +6,12 @@ describe('IncidentsSettingTabs', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(IncidentsSettingTabs);
wrapper = shallowMount(IncidentsSettingTabs, {
provide: {
service: {},
serviceLevelAgreementSettings: {},
},
});
});
afterEach(() => {

View File

@ -1,4 +1,4 @@
import * as Sentry from '@sentry/browser';
import * as Sentry from '~/sentry/wrapper';
import SentryConfig from '~/sentry/sentry_config';
describe('SentryConfig', () => {

View File

@ -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();
});
});
});

View File

@ -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)

View File

@ -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
%(

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"