diff options
Diffstat (limited to 'app')
22 files changed, 339 insertions, 52 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/bar.vue b/app/assets/javascripts/monitoring/components/charts/bar.vue new file mode 100644 index 00000000000..01fd8940dad --- /dev/null +++ b/app/assets/javascripts/monitoring/components/charts/bar.vue @@ -0,0 +1,97 @@ +<script> +import { GlResizeObserverDirective } from '@gitlab/ui'; +import { GlBarChart } from '@gitlab/ui/dist/charts'; +import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; +import { chartHeight } from '../../constants'; +import { barChartsDataParser, graphDataValidatorForValues } from '../../utils'; + +export default { + components: { + GlBarChart, + }, + directives: { + GlResizeObserverDirective, + }, + props: { + graphData: { + type: Object, + required: true, + validator: graphDataValidatorForValues.bind(null, false), + }, + }, + data() { + return { + width: 0, + height: chartHeight, + svgs: {}, + }; + }, + computed: { + chartData() { + return barChartsDataParser(this.graphData.metrics); + }, + chartOptions() { + return { + dataZoom: [this.dataZoomConfig], + }; + }, + xAxisTitle() { + const { xLabel = '' } = this.graphData; + return xLabel; + }, + yAxisTitle() { + const { y_label = '' } = this.graphData; + return y_label; // eslint-disable-line babel/camelcase + }, + xAxisType() { + const { x_type = 'value' } = this.graphData; + return x_type; // eslint-disable-line babel/camelcase + }, + dataZoomConfig() { + const handleIcon = this.svgs['scroll-handle']; + + return handleIcon ? { handleIcon } : {}; + }, + }, + created() { + this.setSvg('scroll-handle'); + }, + methods: { + formatLegendLabel(query) { + return `${query.label}`; + }, + onResize() { + if (!this.$refs.barChart) return; + const { width } = this.$refs.barChart.$el.getBoundingClientRect(); + this.width = width; + }, + setSvg(name) { + getSvgIconPathContent(name) + .then(path => { + if (path) { + this.$set(this.svgs, name, `path://${path}`); + } + }) + .catch(e => { + // eslint-disable-next-line no-console, @gitlab/require-i18n-strings + console.error('SVG could not be rendered correctly: ', e); + }); + }, + }, +}; +</script> +<template> + <div v-gl-resize-observer-directive="onResize"> + <gl-bar-chart + ref="barChart" + v-bind="$attrs" + :data="chartData" + :option="chartOptions" + :width="width" + :height="height" + :x-axis-title="xAxisTitle" + :y-axis-title="yAxisTitle" + :x-axis-type="xAxisType" + /> + </div> +</template> diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index 79f2c8ad41f..da305c7dda3 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -18,6 +18,7 @@ import MonitorAnomalyChart from './charts/anomaly.vue'; import MonitorSingleStatChart from './charts/single_stat.vue'; import MonitorHeatmapChart from './charts/heatmap.vue'; import MonitorColumnChart from './charts/column.vue'; +import MonitorBarChart from './charts/bar.vue'; import MonitorStackedColumnChart from './charts/stacked_column.vue'; import MonitorEmptyChart from './charts/empty_chart.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; @@ -31,6 +32,7 @@ export default { components: { MonitorSingleStatChart, MonitorColumnChart, + MonitorBarChart, MonitorHeatmapChart, MonitorStackedColumnChart, MonitorEmptyChart, @@ -259,6 +261,10 @@ export default { v-else-if="isPanelType('heatmap') && graphDataHasMetrics" :graph-data="graphData" /> + <monitor-bar-chart + v-else-if="isPanelType('bar') && graphDataHasMetrics" + :graph-data="graphData" + /> <monitor-column-chart v-else-if="isPanelType('column') && graphDataHasMetrics" :graph-data="graphData" diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index cc7f5af2259..4fd6903d566 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -46,7 +46,6 @@ export const metricStates = { }; export const sidebarAnimationDuration = 300; // milliseconds. - export const chartHeight = 300; export const graphTypes = { diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index 066d0bf7676..48ed2259a51 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -73,14 +73,21 @@ const mapToMetricsViewModel = (metrics, defaultLabel) => })); /** - * Maps an axis view model + * Maps X-axis view model + * + * @param {Object} axis + */ +const mapXAxisToViewModel = ({ name = '' }) => ({ name }); + +/** + * Maps Y-axis view model * * Defaults to a 2 digit precision and `number` format. It only allows * formats in the SUPPORTED_FORMATS array. * * @param {Object} axis */ -const mapToAxisViewModel = ({ name = '', format = SUPPORTED_FORMATS.number, precision = 2 }) => { +const mapYAxisToViewModel = ({ name = '', format = SUPPORTED_FORMATS.number, precision = 2 }) => { return { name, format: SUPPORTED_FORMATS[format] || SUPPORTED_FORMATS.number, @@ -94,15 +101,30 @@ const mapToAxisViewModel = ({ name = '', format = SUPPORTED_FORMATS.number, prec * @param {Object} panel - Metrics panel * @returns {Object} */ -const mapToPanelViewModel = ({ title = '', type, y_label, y_axis = {}, metrics = [] }) => { +const mapPanelToViewModel = ({ + title = '', + type, + x_axis = {}, + x_label, + y_label, + y_axis = {}, + metrics = [], +}) => { + // Both `x_axis.name` and `x_label` are supported for now + // https://gitlab.com/gitlab-org/gitlab/issues/210521 + const xAxis = mapXAxisToViewModel({ name: x_label, ...x_axis }); // eslint-disable-line babel/camelcase + // Both `y_axis.name` and `y_label` are supported for now // https://gitlab.com/gitlab-org/gitlab/issues/208385 - const yAxis = mapToAxisViewModel({ name: y_label, ...y_axis }); // eslint-disable-line babel/camelcase + const yAxis = mapYAxisToViewModel({ name: y_label, ...y_axis }); // eslint-disable-line babel/camelcase + return { title, type, + xLabel: xAxis.name, y_label: yAxis.name, // Changing y_label to yLabel is pending https://gitlab.com/gitlab-org/gitlab/issues/207198 yAxis, + xAxis, metrics: mapToMetricsViewModel(metrics, yAxis.name), }; }; @@ -117,7 +139,7 @@ const mapToPanelGroupViewModel = ({ group = '', panels = [] }, i) => { return { key: `${slugify(group || 'default')}-${i}`, group, - panels: panels.map(mapToPanelViewModel), + panels: panels.map(mapPanelToViewModel), }; }; diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 6a46c7e67e4..7c6cd19eb7b 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -132,4 +132,63 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => { return mergeUrlParams(params, toUrl); }; +/** + * Get the metric value from first data point. + * Currently only used for bar charts + * + * @param {Array} values data points + * @returns {Number} + */ +const metricValueMapper = values => values[0]?.[1]; + +/** + * Get the metric name from metric object + * Currently only used for bar charts + * e.g. { handler: '/query' } + * { method: 'get' } + * + * @param {Object} metric metric object + * @returns {String} + */ +const metricNameMapper = metric => Object.values(metric)?.[0]; + +/** + * Parse metric object to extract metric value and name in + * [<metric-value>, <metric-name>] format. + * Currently only used for bar charts + * + * @param {Object} param0 metric object + * @returns {Array} + */ +const resultMapper = ({ metric, values = [] }) => [ + metricValueMapper(values), + metricNameMapper(metric), +]; + +/** + * Bar charts graph data parser to massage data from + * backend to a format acceptable by bar charts component + * in GitLab UI + * + * e.g. + * { + * SLO: [ + * [98, 'api'], + * [99, 'web'], + * [99, 'database'] + * ] + * } + * + * @param {Array} data series information + * @returns {Object} + */ +export const barChartsDataParser = (data = []) => + data?.reduce( + (acc, { result = [], label }) => ({ + ...acc, + [label]: result.map(resultMapper), + }), + {}, + ); + export default {}; diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss index 30ef047bf04..5872c90a25e 100644 --- a/app/assets/stylesheets/pages/prometheus.scss +++ b/app/assets/stylesheets/pages/prometheus.scss @@ -93,6 +93,11 @@ .alert-current-setting { max-width: 240px; + + .badge.badge-danger { + color: $red-500; + background-color: $red-100; + } } .prometheus-graph-cursor { diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 26d73007e65..1f1ff75359d 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -30,8 +30,7 @@ module AuthHelper def qa_class_for_provider(provider) { - saml: 'qa-saml-login-button', - github: 'qa-github-login-button' + saml: 'qa-saml-login-button' }[provider.to_sym] end diff --git a/app/models/concerns/services/data_fields.rb b/app/models/concerns/services/data_fields.rb new file mode 100644 index 00000000000..10963e4e7d8 --- /dev/null +++ b/app/models/concerns/services/data_fields.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Services + module DataFields + extend ActiveSupport::Concern + + included do + belongs_to :service + + delegate :activated?, to: :service, allow_nil: true + + validates :service, presence: true + end + + class_methods do + def encryption_options + { + key: Settings.attr_encrypted_db_key_base_32, + encode: true, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm' + } + end + end + end +end diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb index cf406a784ce..12ebf260e08 100644 --- a/app/models/project_services/data_fields.rb +++ b/app/models/project_services/data_fields.rb @@ -44,6 +44,7 @@ module DataFields included do has_one :issue_tracker_data, autosave: true has_one :jira_tracker_data, autosave: true + has_one :open_project_tracker_data, autosave: true def data_fields raise NotImplementedError diff --git a/app/models/project_services/issue_tracker_data.rb b/app/models/project_services/issue_tracker_data.rb index b1d67657fe6..414f2c1da4d 100644 --- a/app/models/project_services/issue_tracker_data.rb +++ b/app/models/project_services/issue_tracker_data.rb @@ -1,20 +1,7 @@ # frozen_string_literal: true class IssueTrackerData < ApplicationRecord - belongs_to :service - - delegate :activated?, to: :service, allow_nil: true - - validates :service, presence: true - - def self.encryption_options - { - key: Settings.attr_encrypted_db_key_base_32, - encode: true, - mode: :per_attribute_iv, - algorithm: 'aes-256-gcm' - } - end + include Services::DataFields attr_encrypted :project_url, encryption_options attr_encrypted :issues_url, encryption_options diff --git a/app/models/project_services/jira_tracker_data.rb b/app/models/project_services/jira_tracker_data.rb index e4e0f64150a..f24ba8877d2 100644 --- a/app/models/project_services/jira_tracker_data.rb +++ b/app/models/project_services/jira_tracker_data.rb @@ -1,20 +1,7 @@ # frozen_string_literal: true class JiraTrackerData < ApplicationRecord - belongs_to :service - - delegate :activated?, to: :service, allow_nil: true - - validates :service, presence: true - - def self.encryption_options - { - key: Settings.attr_encrypted_db_key_base_32, - encode: true, - mode: :per_attribute_iv, - algorithm: 'aes-256-gcm' - } - end + include Services::DataFields attr_encrypted :url, encryption_options attr_encrypted :api_url, encryption_options diff --git a/app/models/project_services/open_project_service.rb b/app/models/project_services/open_project_service.rb new file mode 100644 index 00000000000..a24fbc1611d --- /dev/null +++ b/app/models/project_services/open_project_service.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class OpenProjectService < IssueTrackerService + validates :url, public_url: true, presence: true, if: :activated? + validates :api_url, public_url: true, allow_blank: true, if: :activated? + validates :token, presence: true, if: :activated? + validates :project_identifier_code, presence: true, if: :activated? + + data_field :url, :api_url, :token, :closed_status_id, :project_identifier_code + + def data_fields + open_project_tracker_data || self.build_open_project_tracker_data + end + + def self.to_param + 'open_project' + end +end diff --git a/app/models/project_services/open_project_tracker_data.rb b/app/models/project_services/open_project_tracker_data.rb new file mode 100644 index 00000000000..20de60e40c1 --- /dev/null +++ b/app/models/project_services/open_project_tracker_data.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class OpenProjectTrackerData < ApplicationRecord + include Services::DataFields + + # When the Open Project is fresh installed, the default closed status id is "13" based on current version: v8. + DEFAULT_CLOSED_STATUS_ID = "13" + + attr_encrypted :url, encryption_options + attr_encrypted :api_url, encryption_options + attr_encrypted :token, encryption_options + + def closed_status_id + super || DEFAULT_CLOSED_STATUS_ID + end +end diff --git a/app/models/terraform.rb b/app/models/terraform.rb new file mode 100644 index 00000000000..3d974ffe051 --- /dev/null +++ b/app/models/terraform.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Terraform + def self.table_name_prefix + 'terraform_' + end +end diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb new file mode 100644 index 00000000000..8ca4ee9239a --- /dev/null +++ b/app/models/terraform/state.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Terraform + class State < ApplicationRecord + belongs_to :project + + validates :project_id, presence: true + + after_save :update_file_store, if: :saved_change_to_file? + + mount_uploader :file, StateUploader + + def update_file_store + # The file.object_store is set during `uploader.store!` + # which happens after object is inserted/updated + self.update_column(:file_store, file.object_store) + end + + def file_store + super || StateUploader.default_store + end + end +end diff --git a/app/services/merge_requests/after_create_service.rb b/app/services/merge_requests/after_create_service.rb index 6da30f8af16..bc681397039 100644 --- a/app/services/merge_requests/after_create_service.rb +++ b/app/services/merge_requests/after_create_service.rb @@ -6,11 +6,8 @@ module MergeRequests event_service.open_mr(merge_request, current_user) notification_service.new_merge_request(merge_request, current_user) - # https://gitlab.com/gitlab-org/gitlab/issues/208813 - if ::Feature.enabled?(:create_merge_request_pipelines_in_sidekiq, project) - create_pipeline_for(merge_request, current_user) - merge_request.update_head_pipeline - end + create_pipeline_for(merge_request, current_user) + merge_request.update_head_pipeline merge_request.diffs(include_stats: false).write_cache merge_request.create_cross_references!(current_user) diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index d957a6425b2..1cdfba41432 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -21,12 +21,6 @@ module MergeRequests todo_service.new_merge_request(issuable, current_user) issuable.cache_merge_request_closes_issues!(current_user) - # https://gitlab.com/gitlab-org/gitlab/issues/208813 - unless ::Feature.enabled?(:create_merge_request_pipelines_in_sidekiq, project) - create_pipeline_for(issuable, current_user) - issuable.update_head_pipeline - end - Gitlab::UsageDataCounters::MergeRequestCounter.count(:create) link_lfs_objects(issuable) diff --git a/app/uploaders/terraform/state_uploader.rb b/app/uploaders/terraform/state_uploader.rb new file mode 100644 index 00000000000..9c5ae8a8bdc --- /dev/null +++ b/app/uploaders/terraform/state_uploader.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Terraform + class StateUploader < GitlabUploader + include ObjectStorage::Concern + + storage_options Gitlab.config.terraform_state + + delegate :project_id, to: :model + + # Use Lockbox to encrypt/decrypt the stored file (registers CarrierWave callbacks) + encrypt(key: :key) + + def filename + "#{model.id}.tfstate" + end + + def store_dir + project_id.to_s + end + + def key + OpenSSL::HMAC.digest('SHA256', Gitlab::Application.secrets.db_key_base, project_id.to_s) + end + + class << self + def direct_upload_enabled? + false + end + + def background_upload_enabled? + false + end + + def proxy_download_enabled? + true + end + + def default_store + object_store_enabled? ? ObjectStorage::Store::REMOTE : ObjectStorage::Store::LOCAL + end + end + end +end diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml index 849f92fdc95..c47a8891fd5 100644 --- a/app/views/kaminari/gitlab/_gap.html.haml +++ b/app/views/kaminari/gitlab/_gap.html.haml @@ -5,4 +5,4 @@ -# per_page: number of items to fetch per page -# remote: data-remote %li.page-item.disabled.d-none.d-md-block - = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' + = link_to raw(t('views.pagination.truncate')), '#', class: 'page-link' diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 1a1da6b3801..e20573ed3a7 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -129,7 +129,7 @@ = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar - - if Feature.enabled?(:save_issuable_health_status, @project.group) && issuable_sidebar[:type] == "issue" + - if issuable_sidebar.dig(:features_available, :health_status) .js-sidebar-status-entry-point - if issuable_sidebar.has_key?(:confidential) diff --git a/app/views/shared/snippets/show.js.haml b/app/views/shared/snippets/show.js.haml index a9af732bbb5..d552c1a723b 100644 --- a/app/views/shared/snippets/show.js.haml +++ b/app/views/shared/snippets/show.js.haml @@ -1,2 +1,2 @@ -document.write('#{escape_javascript(stylesheet_link_tag "#{stylesheet_url 'snippets'}")}'); -document.write('#{escape_javascript(render 'shared/snippets/embed')}'); +document.write('#{escape_javascript(stylesheet_link_tag("#{stylesheet_url 'snippets'}"))}'); +document.write('#{escape_javascript(render('shared/snippets/embed'))}'); diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 91ab0d69ad1..73bc050d7be 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -133,7 +133,7 @@ class IrkerWorker # rubocop:disable Scalability/IdempotentWorker commit = commit_from_id project, hook_attrs['id'] sha = colorize_sha Commit.truncate_sha(hook_attrs['id']) author = hook_attrs['author']['name'] - files = colorize_nb_files(files_count commit) + files = colorize_nb_files(files_count(commit)) title = commit.title sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}" |