diff options
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">·</span> + <span v-else class="d-none d-sm-inline mr-1">·</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 |