Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js10
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js15
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js1
-rw-r--r--app/assets/javascripts/monitoring/utils.js23
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue37
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue21
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue10
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue6
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/controllers/projects/pages_domains_controller.rb10
-rw-r--r--app/views/projects/pages_domains/_certificate.html.haml8
-rw-r--r--app/views/projects/pages_domains/_dns.html.haml8
-rw-r--r--app/views/projects/pages_domains/_form.html.haml10
-rw-r--r--app/views/projects/pages_domains/_lets_encrypt_callout.html.haml10
-rw-r--r--app/views/projects/pages_domains/new.html.haml2
-rw-r--r--app/views/projects/pages_domains/show.html.haml8
-rw-r--r--changelogs/unreleased/207471-expose-note-confidential-attribute-in-apis-and-display-on-frontend.yml5
-rw-r--r--changelogs/unreleased/216728-actionview-template-error-undefined-method-pages_domain-for-pagesd.yml5
-rw-r--r--changelogs/unreleased/jivanvl-use-metrics-url-query-param.yml5
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb16
-rw-r--r--spec/features/projects/pages_spec.rb11
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js24
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js14
-rw-r--r--spec/frontend/monitoring/utils_spec.js37
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js31
-rw-r--r--spec/frontend/notes/components/note_header_spec.js12
-rw-r--r--spec/frontend/sidebar/assignees_realtime_spec.js4
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb2
32 files changed, 319 insertions, 49 deletions
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 2bbf9ef9d78..c2c1ceab312 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -4,6 +4,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import store from './stores';
+import { promCustomVariablesFromUrl } from './utils';
Vue.use(GlToast);
@@ -13,6 +14,8 @@ export default (props = {}) => {
if (el && el.dataset) {
const [currentDashboard] = getParameterValues('dashboard');
+ store.dispatch('monitoringDashboard/setVariables', promCustomVariablesFromUrl());
+
// eslint-disable-next-line no-new
new Vue({
el,
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 1db65f5e960..9d18629bf34 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -80,6 +80,10 @@ export const setTimeRange = ({ commit }, 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) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
@@ -218,12 +222,16 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => {
*
* @param {metric} metric
*/
-export const fetchPrometheusMetric = ({ commit }, { metric, defaultQueryParams }) => {
+export const fetchPrometheusMetric = ({ commit, state }, { metric, defaultQueryParams }) => {
const queryParams = { ...defaultQueryParams };
if (metric.step) {
queryParams.step = metric.step;
}
+ if (state.promVariables.length > 0) {
+ queryParams.variables = state.promVariables;
+ }
+
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams)
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index f868fc4d40b..ebe89e93ede 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -1,7 +1,8 @@
-// Dashboard "skeleton", groups, panels and metrics
+// Dashboard "skeleton", groups, panels, metrics, query variables
export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD';
export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS';
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
+export const SET_PROM_QUERY_VARIABLES = 'SET_PROM_QUERY_VARIABLES';
// Annotations
export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 5efca8215e4..c4c15993aa0 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -51,6 +51,18 @@ const emptyStateFromError = 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 {
/**
* Dashboard panels structure and global state
@@ -169,4 +181,7 @@ export default {
state.expandedPanel.group = group;
state.expandedPanel.panel = panel;
},
+ [types.SET_PROM_QUERY_VARIABLES](state, variables) {
+ state.promVariables = transformVariablesObjectArray(variables);
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index af116f6b98f..3a63d6279f4 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -33,6 +33,7 @@ export default () => ({
panel: null,
},
allDashboards: [],
+ promVariables: [],
// Other project data
annotations: [],
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 40f5eb765b4..9e7f4b05420 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -1,3 +1,4 @@
+import { omit } from 'lodash';
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import {
timeRangeParamNames,
@@ -6,6 +7,13 @@ import {
} 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
* that needs a time series as a response from a prometheus query (queryRange) is
* of a valid format or not.
@@ -114,6 +122,21 @@ export const timeRangeFromUrl = (search = window.location.search) => {
};
/**
+ * 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.
*
* @param {String} New URL
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index a070cf8866a..2d7692fff7a 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -3,7 +3,15 @@ import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
import { isEmpty } from 'lodash';
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 TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
@@ -24,6 +32,7 @@ import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'CommentForm',
@@ -36,11 +45,16 @@ export default {
loadingButton,
TimelineEntryItem,
GlAlert,
+ GlFormCheckbox,
+ GlIcon,
GlIntersperse,
GlLink,
GlSprintf,
},
- mixins: [issuableStateMixin],
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [issuableStateMixin, glFeatureFlagsMixin()],
props: {
noteableType: {
type: String,
@@ -51,6 +65,7 @@ export default {
return {
note: '',
noteType: constants.COMMENT,
+ noteIsConfidential: false,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
@@ -138,6 +153,9 @@ export default {
trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
},
+ confidentialNotesEnabled() {
+ return Boolean(this.glFeatures.confidentialNotes);
+ },
},
watch: {
note(newNote) {
@@ -185,6 +203,7 @@ export default {
note: {
noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id,
+ confidential: this.noteIsConfidential,
note: this.note,
},
merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
@@ -285,6 +304,7 @@ export default {
if (shouldClear) {
this.note = '';
+ this.noteIsConfidential = false;
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
@@ -411,6 +431,19 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
</p>
</gl-alert>
<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
class="float-left btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 74a0b69bc54..54b81a7f310 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,5 +1,6 @@
<script>
import { mapActions } from 'vuex';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue';
@@ -7,6 +8,10 @@ export default {
components: {
timeAgoTooltip,
GitlabTeamMemberBadge,
+ GlIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
},
props: {
author: {
@@ -44,6 +49,11 @@ export default {
required: false,
default: true,
},
+ isConfidential: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -160,7 +170,7 @@ export default {
</span>
</template>
<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>
<template v-if="createdAt">
<span ref="actionText" class="system-note-separator">
@@ -177,6 +187,15 @@ export default {
</a>
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
</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>
<i
v-if="showSpinner"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index dea782683f2..765464d67e4 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -255,10 +255,16 @@ export default {
</div>
<div class="timeline-content">
<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>
<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-actions
:author-id="author.id"
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
index f6646823c5c..bf0c52b2341 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
@@ -4,6 +4,7 @@ import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
import actionCable from '~/actioncable_consumer';
export default {
+ subscription: null,
name: 'AssigneesRealtime',
props: {
mediator: {
@@ -36,6 +37,9 @@ export default {
mounted() {
this.initActionCablePolling();
},
+ beforeDestroy() {
+ this.$options.subscription.unsubscribe();
+ },
methods: {
received(data) {
if (data.event === 'updated') {
@@ -43,7 +47,7 @@ export default {
}
},
initActionCablePolling() {
- actionCable.subscriptions.create(
+ this.$options.subscription = actionCable.subscriptions.create(
{
channel: 'IssuesChannel',
project_path: this.projectPath,
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index bed147aa3a7..e79842b646f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -660,10 +660,6 @@ $note-form-margin-left: 72px;
padding-bottom: 0;
}
-.note-headline-light {
- display: inline;
-}
-
.note-headline-light,
.discussion-headline-light {
color: $gl-text-color-secondary;
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index da5401ffa00..6eb010c4882 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -52,6 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action only: :show do
push_frontend_feature_flag(:real_time_issue_sidebar, @project)
+ push_frontend_feature_flag(:confidential_notes, @project)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index cdf6f5ce828..cccf8fe4358 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -7,6 +7,8 @@ class Projects::PagesDomainsController < Projects::ApplicationController
before_action :authorize_update_pages!
before_action :domain, except: [:new, :create]
+ helper_method :domain_presenter
+
def show
end
@@ -27,7 +29,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end
def retry_auto_ssl
- PagesDomains::RetryAcmeOrderService.new(@domain.pages_domain).execute
+ PagesDomains::RetryAcmeOrderService.new(@domain).execute
redirect_to project_pages_domain_path(@project, @domain)
end
@@ -88,6 +90,10 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end
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
diff --git a/app/views/projects/pages_domains/_certificate.html.haml b/app/views/projects/pages_domains/_certificate.html.haml
index e95841f2867..11c7e4a950b 100644
--- a/app/views/projects/pages_domains/_certificate.html.haml
+++ b/app/views/projects/pages_domains/_certificate.html.haml
@@ -1,7 +1,7 @@
- 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
-- 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
.form-group.border-section
@@ -36,9 +36,9 @@
= _('Certificate')
.d-flex.justify-content-between.align-items-center.p-3
%span
- = @domain.pages_domain.subject || _('missing')
+ = domain_presenter.pages_domain.subject || _('missing')
= 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?') },
class: 'btn btn-remove btn-sm',
method: :delete
diff --git a/app/views/projects/pages_domains/_dns.html.haml b/app/views/projects/pages_domains/_dns.html.haml
index e4e590f0a98..8e2a9c3bab4 100644
--- a/app/views/projects/pages_domains/_dns.html.haml
+++ b/app/views/projects/pages_domains/_dns.html.haml
@@ -1,5 +1,5 @@
- 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
.row
@@ -13,17 +13,17 @@
%p.form-text.text-muted
= _("To access this domain create a new DNS record")
- 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
.row
.col-sm-2
= _("Verification status")
.col-sm-10
.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 }
= 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
= text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-append
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index e06dab9be06..9e9f60a6f09 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -1,15 +1,15 @@
-- if @domain.errors.any?
+- if domain_presenter.errors.any?
.alert.alert-danger
- - @domain.errors.full_messages.each do |msg|
+ - domain_presenter.errors.full_messages.each do |msg|
= msg
.form-group.border-section
.row
- - if @domain.persisted?
+ - if domain_presenter.persisted?
.col-sm-2
= _("Domain")
.col-sm-10
- = external_link(@domain.url, @domain.url)
+ = external_link(domain_presenter.url, domain_presenter.url)
- else
.col-sm-2
= f.label :domain, _("Domain")
@@ -17,7 +17,7 @@
.input-group
= f.text_field :domain, required: true, autocomplete: "off", class: "form-control"
-- if @domain.persisted?
+- if domain_presenter.persisted?
= render 'dns'
- if Gitlab.config.pages.external_https
diff --git a/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml b/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml
index f2de42b218c..a86637c36b3 100644
--- a/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml
+++ b/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml
@@ -1,6 +1,6 @@
-- if @domain.enabled?
- - if @domain.auto_ssl_enabled
- - if @domain.show_auto_ssl_failed_warning?
+- if domain_presenter.enabled?
+ - if domain_presenter.auto_ssl_enabled
+ - 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) }
.row
.col-sm-10.offset-sm-2
@@ -9,8 +9,8 @@
= icon('warning', class: 'mr-2')
= _("Something went wrong while obtaining the Let's Encrypt certificate.")
.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
- - elsif !@domain.certificate_gitlab_provided?
+ = 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_presenter.certificate_gitlab_provided?
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
.row
.col-sm-10.offset-sm-2
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index 3210bfe9231..f5dc3ccc60e 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -4,7 +4,7 @@
= _("New Pages Domain")
= render 'projects/pages_domains/helper_text'
%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 }
.form-actions
= f.submit _('Create New Domain'), class: "btn btn-success"
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index a08be65d7e4..e1be7335a3f 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -1,10 +1,10 @@
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
-- breadcrumb_title @domain.domain
-- page_title @domain.domain
+- breadcrumb_title domain_presenter.domain
+- page_title domain_presenter.domain
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
-- if verification_enabled && @domain.unverified?
+- if verification_enabled && domain_presenter.unverified?
= content_for :flash_message do
.alert.alert-warning
.container-fluid.container-limited
@@ -14,7 +14,7 @@
= _('Pages Domain')
= render 'projects/pages_domains/helper_text'
%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 }
.form-actions.d-flex.justify-content-between
= f.submit _('Save Changes'), class: "btn btn-success"
diff --git a/changelogs/unreleased/207471-expose-note-confidential-attribute-in-apis-and-display-on-frontend.yml b/changelogs/unreleased/207471-expose-note-confidential-attribute-in-apis-and-display-on-frontend.yml
new file mode 100644
index 00000000000..ed347a93dbc
--- /dev/null
+++ b/changelogs/unreleased/207471-expose-note-confidential-attribute-in-apis-and-display-on-frontend.yml
@@ -0,0 +1,5 @@
+---
+title: Add confidential status support for new comments
+merge_request: 30570
+author:
+type: added
diff --git a/changelogs/unreleased/216728-actionview-template-error-undefined-method-pages_domain-for-pagesd.yml b/changelogs/unreleased/216728-actionview-template-error-undefined-method-pages_domain-for-pagesd.yml
new file mode 100644
index 00000000000..d34e092c9ee
--- /dev/null
+++ b/changelogs/unreleased/216728-actionview-template-error-undefined-method-pages_domain-for-pagesd.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 on creating an invalid domains and verification
+merge_request: 31190
+author:
+type: fixed
diff --git a/changelogs/unreleased/jivanvl-use-metrics-url-query-param.yml b/changelogs/unreleased/jivanvl-use-metrics-url-query-param.yml
new file mode 100644
index 00000000000..0d97385664f
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-use-metrics-url-query-param.yml
@@ -0,0 +1,5 @@
+---
+title: In metrics dashboard use custom variables from URL in queries
+merge_request: 30560
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7cec0bbee5b..ab0866da9de 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9146,9 +9146,15 @@ msgstr ""
msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
msgstr ""
+msgid "FeatureFlags|Flag becomes read only soon"
+msgstr ""
+
msgid "FeatureFlags|Get started with feature flags"
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"
msgstr ""
@@ -12794,6 +12800,9 @@ msgstr ""
msgid "Mark comment as resolved"
msgstr ""
+msgid "Mark this comment as private"
+msgstr ""
+
msgid "Mark this issue as a duplicate of another issue"
msgstr ""
@@ -15586,6 +15595,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private comments are accessible by internal staff only"
+msgstr ""
+
msgid "Private group(s)"
msgstr ""
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index c78c5fe2886..40a6f77f0d6 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -148,16 +148,10 @@ describe Projects::PagesDomainsController do
describe 'POST verify' do
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
- 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
@@ -166,7 +160,9 @@ describe Projects::PagesDomainsController do
end
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
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index f4f70e7efbc..faa2b3c9424 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -158,6 +158,17 @@ shared_examples 'pages settings editing' do
expect(page).to have_content('my.test.domain.com')
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
before do
stub_application_setting(pages_domain_verification_enabled: true)
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index fd8fb4c6418..d9794c34b3b 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -25,6 +25,7 @@ import {
clearExpandedPanel,
setGettingStartedEmptyState,
duplicateSystemDashboard,
+ setVariables,
} from '~/monitoring/stores/actions';
import {
gqClient,
@@ -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', () => {
let dispatch;
let state;
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index ab3debb798d..dd0deef486f 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -364,4 +364,18 @@ describe('Monitoring mutations', () => {
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']);
+ });
+ });
});
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index b7e34853552..21597033e0a 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -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', () => {
const { removeTimeRangeParams } = monitoringUtils;
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index a2c7f0b3767..e695fd8238d 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -24,6 +24,7 @@ describe('issue_comment_form component', () => {
let store;
let wrapper;
let axiosMock;
+ let features = {};
const setupStore = (userData, noteableData) => {
store.dispatch('setUserData', userData);
@@ -37,12 +38,16 @@ describe('issue_comment_form component', () => {
noteableType,
},
store,
+ provide: {
+ glFeatures: features,
+ },
});
};
beforeEach(() => {
axiosMock = new MockAdapter(axios);
store = createStore();
+ features = {};
});
afterEach(() => {
@@ -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', () => {
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 8cb78720c7e..19400e61b9c 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -19,6 +19,7 @@ describe('NoteHeader component', () => {
const findActionText = () => wrapper.find({ ref: 'actionText' });
const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
+ const findConfidentialIndicator = () => wrapper.find({ ref: 'confidentialIndicator' });
const findSpinner = () => wrapper.find({ ref: 'spinner' });
const author = {
@@ -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);
+ });
+ });
});
diff --git a/spec/frontend/sidebar/assignees_realtime_spec.js b/spec/frontend/sidebar/assignees_realtime_spec.js
index d6a6ca18fe8..1c62c52dc67 100644
--- a/spec/frontend/sidebar/assignees_realtime_spec.js
+++ b/spec/frontend/sidebar/assignees_realtime_spec.js
@@ -6,7 +6,9 @@ import Mock from './mock_data';
import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
jest.mock('@rails/actioncable', () => {
- const mockConsumer = { subscriptions: { create: jest.fn() } };
+ const mockConsumer = {
+ subscriptions: { create: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }) },
+ };
return {
createConsumer: jest.fn().mockReturnValue(mockConsumer),
};
diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb
index 7d502e74d10..2de82a63560 100644
--- a/spec/views/projects/pages_domains/show.html.haml_spec.rb
+++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb
@@ -7,7 +7,7 @@ describe 'projects/pages_domains/show' do
before do
assign(:project, project)
- assign(:domain, domain.present)
+ allow(view).to receive(:domain_presenter).and_return(domain.present)
stub_pages_setting(external_https: true)
end