diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-05 15:09:15 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-05 15:09:15 +0300 |
commit | 20d564f1064622ef0623434372ac3ceb03173331 (patch) | |
tree | 000d95440566cd189ea774168c9756bcc8fc5fae /app | |
parent | 26384c9a61da9922b8fa4b8351d4e42d51661b37 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/monitoring/components/charts/stacked_column.vue | 103 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/components/panel_type.vue | 6 | ||||
-rw-r--r-- | app/assets/stylesheets/page_bundles/ide.scss | 10 | ||||
-rw-r--r-- | app/finders/concerns/time_frame_filter.rb | 14 | ||||
-rw-r--r-- | app/finders/milestones_finder.rb | 2 | ||||
-rw-r--r-- | app/graphql/resolvers/concerns/time_frame_arguments.rb | 30 | ||||
-rw-r--r-- | app/graphql/resolvers/milestone_resolver.rb | 50 | ||||
-rw-r--r-- | app/graphql/types/group_type.rb | 4 | ||||
-rw-r--r-- | app/graphql/types/milestone_state_enum.rb | 8 | ||||
-rw-r--r-- | app/graphql/types/milestone_type.rb | 17 | ||||
-rw-r--r-- | app/helpers/search_helper.rb | 13 | ||||
-rw-r--r-- | app/models/milestone.rb | 6 | ||||
-rw-r--r-- | app/presenters/milestone_presenter.rb | 15 | ||||
-rw-r--r-- | app/services/ci/create_job_artifacts_service.rb | 52 | ||||
-rw-r--r-- | app/views/search/_results.html.haml | 3 | ||||
-rw-r--r-- | app/views/search/results/_blob.html.haml | 4 | ||||
-rw-r--r-- | app/views/search/results/_wiki_blob.html.haml | 3 |
17 files changed, 317 insertions, 23 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/stacked_column.vue b/app/assets/javascripts/monitoring/components/charts/stacked_column.vue new file mode 100644 index 00000000000..55ae4a3bdb2 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/charts/stacked_column.vue @@ -0,0 +1,103 @@ +<script> +import { GlResizeObserverDirective } from '@gitlab/ui'; +import { GlStackedColumnChart } from '@gitlab/ui/dist/charts'; +import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; +import { chartHeight } from '../../constants'; +import { graphDataValidatorForValues } from '../../utils'; + +export default { + components: { + GlStackedColumnChart, + }, + directives: { + GlResizeObserverDirective, + }, + props: { + graphData: { + type: Object, + required: true, + validator: graphDataValidatorForValues.bind(null, false), + }, + }, + data() { + return { + width: 0, + height: chartHeight, + svgs: {}, + }; + }, + computed: { + chartData() { + return this.graphData.metrics.map(metric => metric.result[0].values.map(val => val[1])); + }, + xAxisTitle() { + return this.graphData.x_label !== undefined ? this.graphData.x_label : ''; + }, + yAxisTitle() { + return this.graphData.y_label !== undefined ? this.graphData.y_label : ''; + }, + xAxisType() { + return this.graphData.x_type !== undefined ? this.graphData.x_type : 'category'; + }, + groupBy() { + return this.graphData.metrics[0].result[0].values.map(val => val[0]); + }, + dataZoomConfig() { + const handleIcon = this.svgs['scroll-handle']; + + return handleIcon ? { handleIcon } : {}; + }, + chartOptions() { + return { + dataZoom: this.dataZoomConfig, + }; + }, + seriesNames() { + return this.graphData.metrics.map(metric => metric.series_name); + }, + }, + created() { + this.setSvg('scroll-handle'); + }, + methods: { + setSvg(name) { + getSvgIconPathContent(name) + .then(path => { + if (path) { + this.$set(this.svgs, name, `path://${path}`); + } + }) + .catch(e => { + // eslint-disable-next-line no-console, @gitlab/i18n/no-non-i18n-strings + console.error('SVG could not be rendered correctly: ', e); + }); + }, + onResize() { + if (!this.$refs.chart) return; + const { width } = this.$refs.chart.$el.getBoundingClientRect(); + this.width = width; + }, + }, +}; +</script> +<template> + <div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> + <div class="prometheus-graph-header"> + <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> + <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> + </div> + <gl-stacked-column-chart + ref="chart" + v-bind="$attrs" + :data="chartData" + :option="chartOptions" + :x-axis-title="xAxisTitle" + :y-axis-title="yAxisTitle" + :x-axis-type="xAxisType" + :group-by="groupBy" + :width="width" + :height="height" + :series-names="seriesNames" + /> + </div> +</template> diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index 4d067365ed9..6751f3d31e8 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -15,6 +15,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 MonitorStackedColumnChart from './charts/stacked_column.vue'; import MonitorEmptyChart from './charts/empty_chart.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; import { downloadCSVOptions, generateLinkToChartOptions } from '../utils'; @@ -24,6 +25,7 @@ export default { MonitorSingleStatChart, MonitorColumnChart, MonitorHeatmapChart, + MonitorStackedColumnChart, MonitorEmptyChart, Icon, GlDropdown, @@ -121,6 +123,10 @@ export default { v-else-if="isPanelType('column') && graphDataHasMetrics" :graph-data="graphData" /> + <monitor-stacked-column-chart + v-else-if="isPanelType('stacked-column') && graphDataHasMetrics" + :graph-data="graphData" + /> <component :is="monitorChartComponent" v-else-if="graphDataHasMetrics" diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 7fa48c70f41..58279bba4ca 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -160,6 +160,11 @@ $ide-commit-header-height: 48px; height: 0; } +// stylelint-disable selector-class-pattern +// stylelint-disable selector-max-compound-selectors +// stylelint-disable stylelint-gitlab/duplicate-selectors +// stylelint-disable stylelint-gitlab/utility-classes + .blob-editor-container { flex: 1; height: 0; @@ -301,6 +306,11 @@ $ide-commit-header-height: 48px; } } +// stylelint-enable selector-class-pattern +// stylelint-enable selector-max-compound-selectors +// stylelint-enable stylelint-gitlab/duplicate-selectors +// stylelint-enable stylelint-gitlab/utility-classes + .preview-container { flex-grow: 1; position: relative; diff --git a/app/finders/concerns/time_frame_filter.rb b/app/finders/concerns/time_frame_filter.rb new file mode 100644 index 00000000000..e0baba25b64 --- /dev/null +++ b/app/finders/concerns/time_frame_filter.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module TimeFrameFilter + def by_timeframe(items) + return items unless params[:start_date] && params[:start_date] + + start_date = params[:start_date].to_date + end_date = params[:end_date].to_date + + items.within_timeframe(start_date, end_date) + rescue ArgumentError + items + end +end diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 77b55cbb838..cfe648d9f79 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -11,6 +11,7 @@ class MilestonesFinder include FinderMethods + include TimeFrameFilter attr_reader :params @@ -24,6 +25,7 @@ class MilestonesFinder items = by_title(items) items = by_search_title(items) items = by_state(items) + items = by_timeframe(items) order(items) end diff --git a/app/graphql/resolvers/concerns/time_frame_arguments.rb b/app/graphql/resolvers/concerns/time_frame_arguments.rb new file mode 100644 index 00000000000..ef333dd05a5 --- /dev/null +++ b/app/graphql/resolvers/concerns/time_frame_arguments.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module TimeFrameArguments + extend ActiveSupport::Concern + + included do + argument :start_date, Types::TimeType, + required: false, + description: 'List items within a time frame where items.start_date is between startDate and endDate parameters (endDate parameter must be present)' + + argument :end_date, Types::TimeType, + required: false, + description: 'List items within a time frame where items.end_date is between startDate and endDate parameters (startDate parameter must be present)' + end + + def validate_timeframe_params!(args) + return unless args[:start_date].present? || args[:end_date].present? + + error_message = + if args[:start_date].nil? || args[:end_date].nil? + "Both startDate and endDate must be present." + elsif args[:start_date] > args[:end_date] + "startDate is after endDate" + end + + if error_message + raise Gitlab::Graphql::Errors::ArgumentError, error_message + end + end +end diff --git a/app/graphql/resolvers/milestone_resolver.rb b/app/graphql/resolvers/milestone_resolver.rb new file mode 100644 index 00000000000..2e7b6fdfd5f --- /dev/null +++ b/app/graphql/resolvers/milestone_resolver.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Resolvers + class MilestoneResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include TimeFrameArguments + + argument :state, Types::MilestoneStateEnum, + required: false, + description: 'Filter milestones by state' + + type Types::MilestoneType, null: true + + def resolve(**args) + validate_timeframe_params!(args) + + authorize! + + MilestonesFinder.new(milestones_finder_params(args)).execute + end + + private + + def milestones_finder_params(args) + { + state: args[:state] || 'all', + start_date: args[:start_date], + end_date: args[:end_date] + }.merge(parent_id_parameter) + end + + def parent + @parent ||= object.respond_to?(:sync) ? object.sync : object + end + + def parent_id_parameter + if parent.is_a?(Group) + { group_ids: parent.id } + elsif parent.is_a?(Project) + { project_ids: parent.id } + end + end + + # MilestonesFinder does not check for current_user permissions, + # so for now we need to keep it here. + def authorize! + Ability.allowed?(context[:current_user], :read_milestone, parent) || raise_resource_not_available_error! + end + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index d22983f2164..718770ebfbc 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -42,6 +42,10 @@ module Types field :parent, GroupType, null: true, description: 'Parent group', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find } + + field :milestones, Types::MilestoneType.connection_type, null: true, + description: 'Find milestones', + resolver: Resolvers::MilestoneResolver end end diff --git a/app/graphql/types/milestone_state_enum.rb b/app/graphql/types/milestone_state_enum.rb new file mode 100644 index 00000000000..032571ac88f --- /dev/null +++ b/app/graphql/types/milestone_state_enum.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Types + class MilestoneStateEnum < BaseEnum + value 'active' + value 'closed' + end +end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index 9c3afb28674..900f8c6f01d 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -3,25 +3,36 @@ module Types class MilestoneType < BaseObject graphql_name 'Milestone' + description 'Represents a milestone.' + + present_using MilestonePresenter authorize :read_milestone field :id, GraphQL::ID_TYPE, null: false, description: 'ID of the milestone' - field :description, GraphQL::STRING_TYPE, null: true, - description: 'Description of the milestone' + field :title, GraphQL::STRING_TYPE, null: false, description: 'Title of the milestone' - field :state, GraphQL::STRING_TYPE, null: false, + + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Description of the milestone' + + field :state, Types::MilestoneStateEnum, null: false, description: 'State of the milestone' + field :web_path, GraphQL::STRING_TYPE, null: false, method: :milestone_path, + description: 'Web path of the milestone' + field :due_date, Types::TimeType, null: true, description: 'Timestamp of the milestone due date' + field :start_date, Types::TimeType, null: true, description: 'Timestamp of the milestone start date' field :created_at, Types::TimeType, null: false, description: 'Timestamp of milestone creation' + field :updated_at, Types::TimeType, null: false, description: 'Timestamp of last milestone update' end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 9a5c5f274a0..e478f76818f 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -86,19 +86,6 @@ module SearchHelper }).html_safe end - def find_project_for_result_blob(projects, result) - @project - end - - # Used in EE - def blob_projects(results) - nil - end - - def parse_search_result(result) - result - end - # Overriden in EE def search_blob_title(project, path) path diff --git a/app/models/milestone.rb b/app/models/milestone.rb index f709e518047..b3278f48aa9 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -59,6 +59,12 @@ class Milestone < ApplicationRecord where(project_id: projects).or(where(group_id: groups)) end + scope :within_timeframe, -> (start_date, end_date) do + where('start_date is not NULL or due_date is not NULL') + .where('start_date is NULL or start_date <= ?', end_date) + .where('due_date is NULL or due_date >= ?', start_date) + end + scope :order_by_name_asc, -> { order(Arel::Nodes::Ascending.new(arel_table[:title].lower)) } scope :reorder_by_due_date_asc, -> { reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) } diff --git a/app/presenters/milestone_presenter.rb b/app/presenters/milestone_presenter.rb new file mode 100644 index 00000000000..7d9045ddebe --- /dev/null +++ b/app/presenters/milestone_presenter.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class MilestonePresenter < Gitlab::View::Presenter::Delegated + presents :milestone + + def milestone_path + url_builder.milestone_path(milestone) + end + + private + + def url_builder + @url_builder ||= Gitlab::UrlBuilder.new(milestone) + end +end diff --git a/app/services/ci/create_job_artifacts_service.rb b/app/services/ci/create_job_artifacts_service.rb new file mode 100644 index 00000000000..e633dc7f633 --- /dev/null +++ b/app/services/ci/create_job_artifacts_service.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Ci + class CreateJobArtifactsService + ArtifactsExistError = Class.new(StandardError) + + def execute(job, artifacts_file, params, metadata_file: nil) + expire_in = params['expire_in'] || + Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in + + job.job_artifacts.build( + project: job.project, + file: artifacts_file, + file_type: params['artifact_type'], + file_format: params['artifact_format'], + file_sha256: artifacts_file.sha256, + expire_in: expire_in) + + if metadata_file + job.job_artifacts.build( + project: job.project, + file: metadata_file, + file_type: :metadata, + file_format: :gzip, + file_sha256: metadata_file.sha256, + expire_in: expire_in) + end + + job.update(artifacts_expire_in: expire_in) + rescue ActiveRecord::RecordNotUnique => error + return true if sha256_matches_existing_artifact?(job, params['artifact_type'], artifacts_file) + + Gitlab::ErrorTracking.track_exception(error, + job_id: job.id, + project_id: job.project_id, + uploading_type: params['artifact_type'] + ) + + job.errors.add(:base, 'another artifact of the same type already exists') + false + end + + private + + def sha256_matches_existing_artifact?(job, artifact_type, artifacts_file) + existing_artifact = job.job_artifacts.find_by_file_type(artifact_type) + return false unless existing_artifact + + existing_artifact.file_sha256 == artifacts_file.sha256 + end + end +end diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 629a5a045b1..8ada8c875f7 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -32,8 +32,7 @@ .term = render 'shared/projects/list', projects: @search_objects, pipeline_status: false - else - - locals = { projects: blob_projects(@search_objects) } if %w[blobs wiki_blobs].include?(@scope) - = render partial: "search/results/#{@scope.singularize}", collection: @search_objects, locals: locals + = render partial: "search/results/#{@scope.singularize}", collection: @search_objects - if @scope != 'projects' = paginate_collection(@search_objects) diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 4fb72b26955..6e17a25c713 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,7 +1,5 @@ -- project = find_project_for_result_blob(projects, blob) +- project = blob.project - return unless project - -- blob = parse_search_result(blob) - blob_link = project_blob_path(project, tree_join(blob.ref, blob.path)) = render partial: 'search/results/blob_data', locals: { blob: blob, project: project, path: blob.path, blob_link: blob_link } diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index 9afed2bbecc..3040917dd6e 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,5 +1,4 @@ -- project = find_project_for_result_blob(projects, wiki_blob) -- wiki_blob = parse_search_result(wiki_blob) +- project = wiki_blob.project - wiki_blob_link = project_wiki_path(project, wiki_blob.basename) = render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, path: wiki_blob.path, blob_link: wiki_blob_link } |