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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-05 15:09:15 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-05 15:09:15 +0300
commit20d564f1064622ef0623434372ac3ceb03173331 (patch)
tree000d95440566cd189ea774168c9756bcc8fc5fae /app
parent26384c9a61da9922b8fa4b8351d4e42d51661b37 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/monitoring/components/charts/stacked_column.vue103
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue6
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss10
-rw-r--r--app/finders/concerns/time_frame_filter.rb14
-rw-r--r--app/finders/milestones_finder.rb2
-rw-r--r--app/graphql/resolvers/concerns/time_frame_arguments.rb30
-rw-r--r--app/graphql/resolvers/milestone_resolver.rb50
-rw-r--r--app/graphql/types/group_type.rb4
-rw-r--r--app/graphql/types/milestone_state_enum.rb8
-rw-r--r--app/graphql/types/milestone_type.rb17
-rw-r--r--app/helpers/search_helper.rb13
-rw-r--r--app/models/milestone.rb6
-rw-r--r--app/presenters/milestone_presenter.rb15
-rw-r--r--app/services/ci/create_job_artifacts_service.rb52
-rw-r--r--app/views/search/_results.html.haml3
-rw-r--r--app/views/search/results/_blob.html.haml4
-rw-r--r--app/views/search/results/_wiki_blob.html.haml3
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 }