提交 79ddf163 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 ae69a88c
...@@ -4,6 +4,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; ...@@ -4,6 +4,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility'; import { getParameterValues } from '~/lib/utils/url_utility';
import store from './stores'; import store from './stores';
import { promCustomVariablesFromUrl } from './utils';
Vue.use(GlToast); Vue.use(GlToast);
...@@ -13,6 +14,8 @@ export default (props = {}) => { ...@@ -13,6 +14,8 @@ export default (props = {}) => {
if (el && el.dataset) { if (el && el.dataset) {
const [currentDashboard] = getParameterValues('dashboard'); const [currentDashboard] = getParameterValues('dashboard');
store.dispatch('monitoringDashboard/setVariables', promCustomVariablesFromUrl());
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el, el,
......
...@@ -80,6 +80,10 @@ export const setTimeRange = ({ commit }, timeRange) => { ...@@ -80,6 +80,10 @@ export const setTimeRange = ({ commit }, timeRange) => {
commit(types.SET_TIME_RANGE, timeRange); commit(types.SET_TIME_RANGE, timeRange);
}; };
export const setVariables = ({ commit }, variables) => {
commit(types.SET_PROM_QUERY_VARIABLES, variables);
};
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => { export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm); commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData'); dispatch('fetchEnvironmentsData');
...@@ -218,12 +222,16 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => { ...@@ -218,12 +222,16 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => {
* *
* @param {metric} metric * @param {metric} metric
*/ */
export const fetchPrometheusMetric = ({ commit }, { metric, defaultQueryParams }) => { export const fetchPrometheusMetric = ({ commit, state }, { metric, defaultQueryParams }) => {
const queryParams = { ...defaultQueryParams }; const queryParams = { ...defaultQueryParams };
if (metric.step) { if (metric.step) {
queryParams.step = metric.step; queryParams.step = metric.step;
} }
if (state.promVariables.length > 0) {
queryParams.variables = state.promVariables;
}
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId }); commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams) return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams)
......
// Dashboard "skeleton", groups, panels and metrics // Dashboard "skeleton", groups, panels, metrics, query variables
export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD'; export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD';
export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS'; export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS';
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE'; export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
export const SET_PROM_QUERY_VARIABLES = 'SET_PROM_QUERY_VARIABLES';
// Annotations // Annotations
export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS'; export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
......
...@@ -51,6 +51,18 @@ const emptyStateFromError = error => { ...@@ -51,6 +51,18 @@ const emptyStateFromError = error => {
return metricStates.UNKNOWN_ERROR; return metricStates.UNKNOWN_ERROR;
}; };
/**
* Maps an variables object to an array
* @returns {Array} The custom variables array to be send to the API
* in the format of [variable1, variable1_value]
* @param {Object} variables - Custom variables provided by the user
*/
const transformVariablesObjectArray = variables =>
Object.entries(variables)
.flat()
.map(encodeURIComponent);
export default { export default {
/** /**
* Dashboard panels structure and global state * Dashboard panels structure and global state
...@@ -169,4 +181,7 @@ export default { ...@@ -169,4 +181,7 @@ export default {
state.expandedPanel.group = group; state.expandedPanel.group = group;
state.expandedPanel.panel = panel; state.expandedPanel.panel = panel;
}, },
[types.SET_PROM_QUERY_VARIABLES](state, variables) {
state.promVariables = transformVariablesObjectArray(variables);
},
}; };
...@@ -33,6 +33,7 @@ export default () => ({ ...@@ -33,6 +33,7 @@ export default () => ({
panel: null, panel: null,
}, },
allDashboards: [], allDashboards: [],
promVariables: [],
// Other project data // Other project data
annotations: [], annotations: [],
......
import { omit } from 'lodash';
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility'; import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import { import {
timeRangeParamNames, timeRangeParamNames,
...@@ -5,6 +6,13 @@ import { ...@@ -5,6 +6,13 @@ import {
timeRangeToParams, timeRangeToParams,
} from '~/lib/utils/datetime_range'; } from '~/lib/utils/datetime_range';
/**
* List of non time range url parameters
* This will be removed once we add support for free text variables
* via the dashboard yaml files in https://gitlab.com/gitlab-org/gitlab/-/issues/215689
*/
export const dashboardParams = ['dashboard', 'group', 'title', 'y_label'];
/** /**
* This method is used to validate if the graph data format for a chart component * This method is used to validate if the graph data format for a chart component
* that needs a time series as a response from a prometheus query (queryRange) is * that needs a time series as a response from a prometheus query (queryRange) is
...@@ -113,6 +121,21 @@ export const timeRangeFromUrl = (search = window.location.search) => { ...@@ -113,6 +121,21 @@ export const timeRangeFromUrl = (search = window.location.search) => {
return timeRangeFromParams(params); return timeRangeFromParams(params);
}; };
/**
* Returns an array with user defined variables from the URL
*
* @returns {Array} The custom variables defined by the
* user in the URL
* @param {String} New URL
*/
export const promCustomVariablesFromUrl = (search = window.location.search) => {
const params = queryToObject(search);
const paramsToRemove = timeRangeParamNames.concat(dashboardParams);
return omit(params, paramsToRemove);
};
/** /**
* Returns a URL with no time range based on the current URL. * Returns a URL with no time range based on the current URL.
* *
......
...@@ -3,7 +3,15 @@ import $ from 'jquery'; ...@@ -3,7 +3,15 @@ import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import Autosize from 'autosize'; import Autosize from 'autosize';
import { GlAlert, GlIntersperse, GlLink, GlSprintf } from '@gitlab/ui'; import {
GlAlert,
GlFormCheckbox,
GlIcon,
GlIntersperse,
GlLink,
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash'; import Flash from '../../flash';
...@@ -24,6 +32,7 @@ import loadingButton from '../../vue_shared/components/loading_button.vue'; ...@@ -24,6 +32,7 @@ import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue'; import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
name: 'CommentForm', name: 'CommentForm',
...@@ -36,11 +45,16 @@ export default { ...@@ -36,11 +45,16 @@ export default {
loadingButton, loadingButton,
TimelineEntryItem, TimelineEntryItem,
GlAlert, GlAlert,
GlFormCheckbox,
GlIcon,
GlIntersperse, GlIntersperse,
GlLink, GlLink,
GlSprintf, GlSprintf,
}, },
mixins: [issuableStateMixin], directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [issuableStateMixin, glFeatureFlagsMixin()],
props: { props: {
noteableType: { noteableType: {
type: String, type: String,
...@@ -51,6 +65,7 @@ export default { ...@@ -51,6 +65,7 @@ export default {
return { return {
note: '', note: '',
noteType: constants.COMMENT, noteType: constants.COMMENT,
noteIsConfidential: false,
isSubmitting: false, isSubmitting: false,
isSubmitButtonDisabled: true, isSubmitButtonDisabled: true,
}; };
...@@ -138,6 +153,9 @@ export default { ...@@ -138,6 +153,9 @@ export default {
trackingLabel() { trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`); return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
}, },
confidentialNotesEnabled() {
return Boolean(this.glFeatures.confidentialNotes);
},
}, },
watch: { watch: {
note(newNote) { note(newNote) {
...@@ -185,6 +203,7 @@ export default { ...@@ -185,6 +203,7 @@ export default {
note: { note: {
noteable_type: this.noteableType, noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id, noteable_id: this.getNoteableData.id,
confidential: this.noteIsConfidential,
note: this.note, note: this.note,
}, },
merge_request_diff_head_sha: this.getNoteableData.diff_head_sha, merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
...@@ -285,6 +304,7 @@ export default { ...@@ -285,6 +304,7 @@ export default {
if (shouldClear) { if (shouldClear) {
this.note = ''; this.note = '';
this.noteIsConfidential = false;
this.resizeTextarea(); this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false; this.$refs.markdownField.previewMarkdown = false;
} }
...@@ -411,6 +431,19 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input" ...@@ -411,6 +431,19 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
</p> </p>
</gl-alert> </gl-alert>
<div class="note-form-actions"> <div class="note-form-actions">
<div v-if="confidentialNotesEnabled" class="js-confidential-note-toggle mb-4">
<gl-form-checkbox v-model="noteIsConfidential">
<gl-icon name="eye-slash" :size="12" />
{{ __('Mark this comment as private') }}
<gl-icon
v-gl-tooltip:tooltipcontainer.bottom
name="question"
:size="12"
:title="__('Private comments are accessible by internal staff only')"
class="gl-text-gray-800"
/>
</gl-form-checkbox>
</div>
<div <div
class="float-left btn-group class="float-left btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'; import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue';
...@@ -7,6 +8,10 @@ export default { ...@@ -7,6 +8,10 @@ export default {
components: { components: {
timeAgoTooltip, timeAgoTooltip,
GitlabTeamMemberBadge, GitlabTeamMemberBadge,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
}, },
props: { props: {
author: { author: {
...@@ -44,6 +49,11 @@ export default { ...@@ -44,6 +49,11 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
isConfidential: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -160,7 +170,7 @@ export default { ...@@ -160,7 +170,7 @@ export default {
</span> </span>
</template> </template>
<span v-else>{{ __('A deleted user') }}</span> <span v-else>{{ __('A deleted user') }}</span>
<span class="note-headline-light note-headline-meta"> <span class="note-headline-light note-headline-meta d-inline-flex align-items-center">
<span class="system-note-message"> <slot></slot> </span> <span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt"> <template v-if="createdAt">
<span ref="actionText" class="system-note-separator"> <span ref="actionText" class="system-note-separator">
...@@ -177,6 +187,15 @@ export default { ...@@ -177,6 +187,15 @@ export default {
</a> </a>
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" /> <time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
</template> </template>
<gl-icon
v-if="isConfidential"
ref="confidentialIndicator"
v-gl-tooltip:tooltipcontainer.bottom
name="eye-slash"
:size="14"
:title="__('Private comments are accessible by internal staff only')"
class="ml-1 gl-text-gray-800"
/>
<slot name="extra-controls"></slot> <slot name="extra-controls"></slot>
<i <i
v-if="showSpinner" v-if="showSpinner"
......
...@@ -255,10 +255,16 @@ export default { ...@@ -255,10 +255,16 @@ export default {
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div class="note-header"> <div class="note-header">
<note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id"> <note-header
v-once
:author="author"
:created-at="note.created_at"
:note-id="note.id"
:is-confidential="note.confidential"
>
<slot slot="note-header-info" name="note-header-info"></slot> <slot slot="note-header-info" name="note-header-info"></slot>
<span v-if="commit" v-html="actionText"></span> <span v-if="commit" v-html="actionText"></span>
<span v-else class="d-none d-sm-inline">&middot;</span> <span v-else class="d-none d-sm-inline mr-1">&middot;</span>
</note-header> </note-header>
<note-actions <note-actions
:author-id="author.id" :author-id="author.id"
......
...@@ -4,6 +4,7 @@ import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql'; ...@@ -4,6 +4,7 @@ import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
import actionCable from '~/actioncable_consumer'; import actionCable from '~/actioncable_consumer';
export default { export default {
subscription: null,
name: 'AssigneesRealtime', name: 'AssigneesRealtime',
props: { props: {
mediator: { mediator: {
...@@ -36,6 +37,9 @@ export default { ...@@ -36,6 +37,9 @@ export default {
mounted() { mounted() {
this.initActionCablePolling(); this.initActionCablePolling();
}, },
beforeDestroy() {
this.$options.subscription.unsubscribe();
},
methods: { methods: {
received(data) { received(data) {
if (data.event === 'updated') { if (data.event === 'updated') {
...@@ -43,7 +47,7 @@ export default { ...@@ -43,7 +47,7 @@ export default {
} }
}, },
initActionCablePolling() { initActionCablePolling() {
actionCable.subscriptions.create( this.$options.subscription = actionCable.subscriptions.create(
{ {
channel: 'IssuesChannel', channel: 'IssuesChannel',
project_path: this.projectPath, project_path: this.projectPath,
......
...@@ -660,10 +660,6 @@ $note-form-margin-left: 72px; ...@@ -660,10 +660,6 @@ $note-form-margin-left: 72px;
padding-bottom: 0; padding-bottom: 0;
} }
.note-headline-light {
display: inline;
}
.note-headline-light, .note-headline-light,
.discussion-headline-light { .discussion-headline-light {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
......
...@@ -52,6 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -52,6 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action only: :show do before_action only: :show do
push_frontend_feature_flag(:real_time_issue_sidebar, @project) push_frontend_feature_flag(:real_time_issue_sidebar, @project)
push_frontend_feature_flag(:confidential_notes, @project)
end end
around_action :allow_gitaly_ref_name_caching, only: [:discussions] around_action :allow_gitaly_ref_name_caching, only: [:discussions]
......
...@@ -7,6 +7,8 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -7,6 +7,8 @@ class Projects::PagesDomainsController < Projects::ApplicationController
before_action :authorize_update_pages! before_action :authorize_update_pages!
before_action :domain, except: [:new, :create] before_action :domain, except: [:new, :create]
helper_method :domain_presenter
def show def show
end end
...@@ -27,7 +29,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -27,7 +29,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end end
def retry_auto_ssl def retry_auto_ssl
PagesDomains::RetryAcmeOrderService.new(@domain.pages_domain).execute PagesDomains::RetryAcmeOrderService.new(@domain).execute
redirect_to project_pages_domain_path(@project, @domain) redirect_to project_pages_domain_path(@project, @domain)
end end
...@@ -88,6 +90,10 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -88,6 +90,10 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end end
def domain def domain
@domain ||= @project.pages_domains.find_by_domain!(params[:id].to_s).present(current_user: current_user) @domain ||= @project.pages_domains.find_by_domain!(params[:id].to_s)
end
def domain_presenter
@domain_presenter ||= domain.present(current_user: current_user)
end end
end end
- auto_ssl_available = ::Gitlab::LetsEncrypt.enabled? - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?
- auto_ssl_enabled = @domain.auto_ssl_enabled? - auto_ssl_enabled = domain_presenter.auto_ssl_enabled?
- auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled - auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
- has_user_defined_certificate = @domain.certificate && @domain.certificate_user_provided? - has_user_defined_certificate = domain_presenter.certificate && domain_presenter.certificate_user_provided?
- if auto_ssl_available - if auto_ssl_available
.form-group.border-section .form-group.border-section
...@@ -36,9 +36,9 @@ ...@@ -36,9 +36,9 @@
= _('Certificate') = _('Certificate')
.d-flex.justify-content-between.align-items-center.p-3 .d-flex.justify-content-between.align-items-center.p-3
%span %span
= @domain.pages_domain.subject || _('missing') = domain_presenter.pages_domain.subject || _('missing')
= link_to _('Remove'), = link_to _('Remove'),
clean_certificate_project_pages_domain_path(@project, @domain), clean_certificate_project_pages_domain_path(@project, domain_presenter),
data: { confirm: _('Are you sure?') }, data: { confirm: _('Are you sure?') },
class: 'btn btn-remove btn-sm', class: 'btn btn-remove btn-sm',
method: :delete method: :delete
......
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}." - dns_record = "#{domain_presenter.domain} CNAME #{domain_presenter.project.pages_subdomain}.#{Settings.pages.host}."
.form-group.border-section .form-group.border-section
.row .row
...@@ -13,17 +13,17 @@ ...@@ -13,17 +13,17 @@
%p.form-text.text-muted %p.form-text.text-muted
= _("To access this domain create a new DNS record") = _("To access this domain create a new DNS record")
- if verification_enabled - if verification_enabled
- verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}" - verification_record = "#{domain_presenter.verification_domain} TXT #{domain_presenter.keyed_verification_code}"
.form-group.border-section .form-group.border-section
.row .row
.col-sm-2 .col-sm-2
= _("Verification status") = _("Verification status")
.col-sm-10 .col-sm-10
.status-badge .status-badge
- text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success'] - text, status = domain_presenter.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success']
.badge{ class: status } .badge{ class: status }
= text = text
= link_to sprite_icon("redo"), verify_project_pages_domain_path(@project, @domain), method: :post, class: "btn has-tooltip", title: _("Retry verification") = link_to sprite_icon("redo"), verify_project_pages_domain_path(@project, domain_presenter), method: :post, class: "btn has-tooltip", title: _("Retry verification")
.input-group .input-group
= text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-append .input-group-append
......
- if @domain.errors.any? - if domain_presenter.errors.any?
.alert.alert-danger .alert.alert-danger
- @domain.errors.full_messages.each do |msg| - domain_presenter.errors.full_messages.each do |msg|
= msg = msg
.form-group.border-section .form-group.border-section
.row .row
- if @domain.persisted? - if domain_presenter.persisted?
.col-sm-2 .col-sm-2
= _("Domain") = _("Domain")
.col-sm-10 .col-sm-10
= external_link(@domain.url, @domain.url) = external_link(domain_presenter.url, domain_presenter.url)
- else - else
.col-sm-2 .col-sm-2
= f.label :domain, _("Domain") = f.label :domain, _("Domain")
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.input-group .input-group
= f.text_field :domain, required: true, autocomplete: "off", class: "form-control" = f.text_field :domain, required: true, autocomplete: "off", class: "form-control"
- if @domain.persisted? - if domain_presenter.persisted?
= render 'dns' = render 'dns'
- if Gitlab.config.pages.external_https - if Gitlab.config.pages.external_https
......
- if @domain.enabled? - if domain_presenter.enabled?
- if @domain.auto_ssl_enabled - if domain_presenter.auto_ssl_enabled
- if @domain.show_auto_ssl_failed_warning? - if domain_presenter.show_auto_ssl_failed_warning?
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) } .form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
.row .row
.col-sm-10.offset-sm-2 .col-sm-10.offset-sm-2
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
= icon('warning', class: 'mr-2') = icon('warning', class: 'mr-2')
= _("Something went wrong while obtaining the Let's Encrypt certificate.") = _("Something went wrong while obtaining the Let's Encrypt certificate.")
.row.mx-0.mt-3 .row.mx-0.mt-3
= link_to s_('GitLabPagesDomains|Retry'), retry_auto_ssl_project_pages_domain_path(@project, @domain), class: "btn btn-sm btn-grouped btn-warning", method: :post = link_to s_('GitLabPagesDomains|Retry'), retry_auto_ssl_project_pages_domain_path(@project, domain_presenter), class: "btn btn-sm btn-grouped btn-warning", method: :post
- elsif !@domain.certificate_gitlab_provided? - elsif !domain_presenter.certificate_gitlab_provided?
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) } .form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
.row .row
.col-sm-10.offset-sm-2 .col-sm-10.offset-sm-2
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= _("New Pages Domain") = _("New Pages Domain")
= render 'projects/pages_domains/helper_text' = render 'projects/pages_domains/helper_text'
%div %div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, domain_presenter], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f } = render 'form', { f: f }
.form-actions .form-actions
= f.submit _('Create New Domain'), class: "btn btn-success" = f.submit _('Create New Domain'), class: "btn btn-success"
......
- add_to_breadcrumbs _("Pages"), project_pages_path(@project) - add_to_breadcrumbs _("Pages"), project_pages_path(@project)
- breadcrumb_title @domain.domain - breadcrumb_title domain_presenter.domain
- page_title @domain.domain - page_title domain_presenter.domain
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
- if verification_enabled && @domain.unverified? - if verification_enabled && domain_presenter.unverified?
= content_for :flash_message do = content_for :flash_message do
.alert.alert-warning .alert.alert-warning
.container-fluid.container-limited .container-fluid.container-limited
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
= _('Pages Domain') = _('Pages Domain')
= render 'projects/pages_domains/helper_text' = render 'projects/pages_domains/helper_text'
%div %div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, domain_presenter], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f } = render 'form', { f: f }
.form-actions.d-flex.justify-content-between .form-actions.d-flex.justify-content-between
= f.submit _('Save Changes'), class: "btn btn-success" = f.submit _('Save Changes'), class: "btn btn-success"
......
---
title: Add confidential status support for new comments
merge_request: 30570
author:
type: added
---
title: Fix 500 on creating an invalid domains and verification
merge_request: 31190
author:
type: fixed
---
title: In metrics dashboard use custom variables from URL in queries
merge_request: 30560
author:
type: added
...@@ -9146,9 +9146,15 @@ msgstr "" ...@@ -9146,9 +9146,15 @@ msgstr ""
msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality." msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
msgstr "" msgstr ""
msgid "FeatureFlags|Flag becomes read only soon"
msgstr ""
msgid "FeatureFlags|Get started with feature flags" msgid "FeatureFlags|Get started with feature flags"
msgstr "" msgstr ""
msgid "FeatureFlags|GitLab is moving to a new way of managing feature flags, and in 13.4, this feature flag will become read-only. Please create a new feature flag."
msgstr ""
msgid "FeatureFlags|ID" msgid "FeatureFlags|ID"
msgstr "" msgstr ""
...@@ -12794,6 +12800,9 @@ msgstr "" ...@@ -12794,6 +12800,9 @@ msgstr ""
msgid "Mark comment as resolved" msgid "Mark comment as resolved"
msgstr "" msgstr ""
msgid "Mark this comment as private"
msgstr ""
msgid "Mark this issue as a duplicate of another issue" msgid "Mark this issue as a duplicate of another issue"
msgstr "" msgstr ""
...@@ -15586,6 +15595,9 @@ msgstr "" ...@@ -15586,6 +15595,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members." msgid "Private - The group and its projects can only be viewed by members."
msgstr "" msgstr ""
msgid "Private comments are accessible by internal staff only"
msgstr ""
msgid "Private group(s)" msgid "Private group(s)"
msgstr "" msgstr ""
......
...@@ -148,16 +148,10 @@ describe Projects::PagesDomainsController do ...@@ -148,16 +148,10 @@ describe Projects::PagesDomainsController do
describe 'POST verify' do describe 'POST verify' do
let(:params) { request_params.merge(id: pages_domain.domain) } let(:params) { request_params.merge(id: pages_domain.domain) }
def stub_service
service = double(:service)
expect(VerifyPagesDomainService).to receive(:new) { service }
service
end
it 'handles verification success' do it 'handles verification success' do
expect(stub_service).to receive(:execute).and_return(status: :success) expect_next_instance_of(VerifyPagesDomainService, pages_domain) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
post :verify, params: params post :verify, params: params
...@@ -166,7 +160,9 @@ describe Projects::PagesDomainsController do ...@@ -166,7 +160,9 @@ describe Projects::PagesDomainsController do
end end
it 'handles verification failure' do it 'handles verification failure' do
expect(stub_service).to receive(:execute).and_return(status: :failed) expect_next_instance_of(VerifyPagesDomainService, pages_domain) do |service|
expect(service).to receive(:execute).and_return(status: :failed)
end
post :verify, params: params post :verify, params: params
......
...@@ -158,6 +158,17 @@ shared_examples 'pages settings editing' do ...@@ -158,6 +158,17 @@ shared_examples 'pages settings editing' do
expect(page).to have_content('my.test.domain.com') expect(page).to have_content('my.test.domain.com')
end end
it 'shows validation error if domain is duplicated' do
project.pages_domains.create!(domain: 'my.test.domain.com')
visit new_project_pages_domain_path(project)
fill_in 'Domain', with: 'my.test.domain.com'
click_button 'Create New Domain'
expect(page).to have_content('Domain has already been taken')
end
describe 'with dns verification enabled' do describe 'with dns verification enabled' do
before do before do
stub_application_setting(pages_domain_verification_enabled: true) stub_application_setting(pages_domain_verification_enabled: true)
......
...@@ -25,6 +25,7 @@ import { ...@@ -25,6 +25,7 @@ import {
clearExpandedPanel, clearExpandedPanel,
setGettingStartedEmptyState, setGettingStartedEmptyState,
duplicateSystemDashboard, duplicateSystemDashboard,
setVariables,
} from '~/monitoring/stores/actions'; } from '~/monitoring/stores/actions';
import { import {
gqClient, gqClient,
...@@ -392,6 +393,29 @@ describe('Monitoring store actions', () => { ...@@ -392,6 +393,29 @@ describe('Monitoring store actions', () => {
); );
}); });
}); });
describe('setVariables', () => {
let mockedState;
beforeEach(() => {
mockedState = storeState();
});
it('should commit SET_PROM_QUERY_VARIABLES mutation', done => {
testAction(
setVariables,
{ pod: 'POD' },
mockedState,
[
{
type: types.SET_PROM_QUERY_VARIABLES,
payload: { pod: 'POD' },
},
],
[],
done,
);
});
});
describe('fetchDashboard', () => { describe('fetchDashboard', () => {
let dispatch; let dispatch;
let state; let state;
......
...@@ -364,4 +364,18 @@ describe('Monitoring mutations', () => { ...@@ -364,4 +364,18 @@ describe('Monitoring mutations', () => {
expect(stateCopy.expandedPanel.panel).toEqual(null); expect(stateCopy.expandedPanel.panel).toEqual(null);
}); });
}); });
describe('SET_PROM_QUERY_VARIABLES', () => {
it('stores an empty variables array when no custom variables are given', () => {
mutations[types.SET_PROM_QUERY_VARIABLES](stateCopy, {});
expect(stateCopy.promVariables).toEqual([]);
});
it('stores variables in the key key_value format in the array', () => {
mutations[types.SET_PROM_QUERY_VARIABLES](stateCopy, { pod: 'POD', stage: 'main ops' });
expect(stateCopy.promVariables).toEqual(['pod', 'POD', 'stage', 'main%20ops']);
});
});
}); });
...@@ -169,6 +169,43 @@ describe('monitoring/utils', () => { ...@@ -169,6 +169,43 @@ describe('monitoring/utils', () => {
}); });
}); });
describe('promCustomVariablesFromUrl', () => {
const { promCustomVariablesFromUrl } = monitoringUtils;
beforeEach(() => {
jest.spyOn(urlUtils, 'queryToObject');
});
afterEach(() => {
urlUtils.queryToObject.mockRestore();
});
it('returns an object with only the custom variables', () => {
urlUtils.queryToObject.mockReturnValueOnce({
dashboard: '.gitlab/dashboards/custom_dashboard.yml',
y_label: 'memory usage',
group: 'kubernetes',
title: 'Kubernetes memory total',
start: '2020-05-06',
end: '2020-05-07',
duration_seconds: '86400',
direction: 'left',
anchor: 'top',
pod: 'POD',
});
expect(promCustomVariablesFromUrl()).toEqual(expect.objectContaining({ pod: 'POD' }));
});
it('returns an empty object when no custom variables are present', () => {
urlUtils.queryToObject.mockReturnValueOnce({
dashboard: '.gitlab/dashboards/custom_dashboard.yml',
});
expect(promCustomVariablesFromUrl()).toStrictEqual({});
});
});
describe('removeTimeRangeParams', () => { describe('removeTimeRangeParams', () => {
const { removeTimeRangeParams } = monitoringUtils; const { removeTimeRangeParams } = monitoringUtils;
......
...@@ -24,6 +24,7 @@ describe('issue_comment_form component', () => { ...@@ -24,6 +24,7 @@ describe('issue_comment_form component', () => {
let store; let store;
let wrapper; let wrapper;
let axiosMock; let axiosMock;
let features = {};
const setupStore = (userData, noteableData) => { const setupStore = (userData, noteableData) => {
store.dispatch('setUserData', userData); store.dispatch('setUserData', userData);
...@@ -37,12 +38,16 @@ describe('issue_comment_form component', () => { ...@@ -37,12 +38,16 @@ describe('issue_comment_form component', () => {
noteableType, noteableType,
}, },
store, store,
provide: {
glFeatures: features,
},
}); });
}; };
beforeEach(() => { beforeEach(() => {
axiosMock = new MockAdapter(axios); axiosMock = new MockAdapter(axios);
store = createStore(); store = createStore();
features = {};
}); });
afterEach(() => { afterEach(() => {
...@@ -298,6 +303,32 @@ describe('issue_comment_form component', () => { ...@@ -298,6 +303,32 @@ describe('issue_comment_form component', () => {
}); });
}); });
}); });
describe('when note can be confidential', () => {
it('appends confidential status to note payload when saving', () => {
jest.spyOn(wrapper.vm, 'saveNote').mockReturnValue(new Promise(() => {}));
wrapper.vm.note = 'confidential note';
return wrapper.vm.$nextTick().then(() => {
wrapper.find('.js-comment-submit-button').trigger('click');
const [providedData] = wrapper.vm.saveNote.mock.calls[0];
expect(providedData.data.note.confidential).toBe(false);
});
});
it('should render confidential toggle as false', () => {
features = { confidentialNotes: true };
mountComponent();
const input = wrapper.find('.js-confidential-note-toggle .form-check-input');
expect(input.exists()).toBe(true);
expect(input.attributes('checked')).toBeFalsy();
});
});
}); });
describe('issue is confidential', () => { describe('issue is confidential', () => {
......
...@@ -19,6 +19,7 @@ describe('NoteHeader component', () => { ...@@ -19,6 +19,7 @@ describe('NoteHeader component', () => {
const findActionText = () => wrapper.find({ ref: 'actionText' }); const findActionText = () => wrapper.find({ ref: 'actionText' });
const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' }); const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' }); const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
const findConfidentialIndicator = () => wrapper.find({ ref: 'confidentialIndicator' });
const findSpinner = () => wrapper.find({ ref: 'spinner' }); const findSpinner = () => wrapper.find({ ref: 'spinner' });
const author = { const author = {
...@@ -246,4 +247,15 @@ describe('NoteHeader component', () => { ...@@ -246,4 +247,15 @@ describe('NoteHeader component', () => {
}); });
}); });
}); });
describe('with confidentiality indicator', () => {
it.each`
status | condition
${true} | ${'shows'}
${false} | ${'hides'}
`('$condition icon indicator when isConfidential is $status', ({ status }) => {
createComponent({ isConfidential: status });
expect(findConfidentialIndicator().exists()).toBe(status);
});
});
}); });
...@@ -6,7 +6,9 @@ import Mock from './mock_data'; ...@@ -6,7 +6,9 @@ import Mock from './mock_data';
import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql'; import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
jest.mock('@rails/actioncable', () => { jest.mock('@rails/actioncable', () => {
const mockConsumer = { subscriptions: { create: jest.fn() } }; const mockConsumer = {
subscriptions: { create: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }) },
};
return { return {
createConsumer: jest.fn().mockReturnValue(mockConsumer), createConsumer: jest.fn().mockReturnValue(mockConsumer),
}; };
......
...@@ -7,7 +7,7 @@ describe 'projects/pages_domains/show' do ...@@ -7,7 +7,7 @@ describe 'projects/pages_domains/show' do
before do before do
assign(:project, project) assign(:project, project)
assign(:domain, domain.present) allow(view).to receive(:domain_presenter).and_return(domain.present)
stub_pages_setting(external_https: true) stub_pages_setting(external_https: true)
end end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册