diff options
Diffstat (limited to 'app')
28 files changed, 370 insertions, 48 deletions
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index 0fdd4d4f33d..050e2d1079b 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -11,7 +11,7 @@ class Projects::JobsController < Projects::ApplicationController before_action :authorize_erase_build!, only: [:erase] before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :verify_api_request!, only: :terminal_websocket_authorize - before_action only: [:trace] do + before_action only: [:show] do push_frontend_feature_flag(:job_log_json) end @@ -67,38 +67,27 @@ class Projects::JobsController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def trace - if Feature.enabled?(:job_log_json, @project) - json_trace - else - html_trace - end - end - - def html_trace build.trace.read do |stream| respond_to do |format| format.json do - result = { - id: @build.id, status: @build.status, complete: @build.complete? - } - - if stream.valid? - stream.limit - state = params[:state].presence - trace = stream.html_with_state(state) - result.merge!(trace.to_h) - end - - render json: result + # TODO: when the feature flag is removed we should not pass + # content_format to serialize method. + content_format = Feature.enabled?(:job_log_json, @project) ? :json : :html + + build_trace = Ci::BuildTrace.new( + build: @build, + stream: stream, + state: params[:state], + content_format: content_format) + + render json: BuildTraceSerializer + .new(project: @project, current_user: @current_user) + .represent(build_trace) end end end end - def json_trace - # will be implemented with https://gitlab.com/gitlab-org/gitlab-foss/issues/66454 - end - def retry return respond_422 unless @build.retryable? diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 2932e558a37..2b46e51290f 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -33,6 +33,8 @@ class TodosFinder end def execute + return Todo.none if current_user.nil? + items = current_user.todos items = by_action_id(items) items = by_action(items) @@ -180,11 +182,9 @@ class TodosFinder end def by_group(items) - if group? - items.for_group_and_descendants(group) - else - items - end + return items unless group? + + items.for_group_ids_and_descendants(params[:group_id]) end def by_state(items) diff --git a/app/graphql/mutations/concerns/mutations/resolves_group.rb b/app/graphql/mutations/concerns/mutations/resolves_group.rb new file mode 100644 index 00000000000..4306ce512f1 --- /dev/null +++ b/app/graphql/mutations/concerns/mutations/resolves_group.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Mutations + module ResolvesGroup + extend ActiveSupport::Concern + + def resolve_group(full_path:) + resolver.resolve(full_path: full_path) + end + + def resolver + Resolvers::GroupResolver.new(object: nil, context: context) + end + end +end diff --git a/app/graphql/resolvers/todo_resolver.rb b/app/graphql/resolvers/todo_resolver.rb new file mode 100644 index 00000000000..38a4539f34a --- /dev/null +++ b/app/graphql/resolvers/todo_resolver.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module Resolvers + class TodoResolver < BaseResolver + type Types::TodoType, null: true + + alias_method :user, :object + + argument :action, [Types::TodoActionEnum], + required: false, + description: 'The action to be filtered' + + argument :author_id, [GraphQL::ID_TYPE], + required: false, + description: 'The ID of an author' + + argument :project_id, [GraphQL::ID_TYPE], + required: false, + description: 'The ID of a project' + + argument :group_id, [GraphQL::ID_TYPE], + required: false, + description: 'The ID of a group' + + argument :state, [Types::TodoStateEnum], + required: false, + description: 'The state of the todo' + + argument :type, [Types::TodoTargetEnum], + required: false, + description: 'The type of the todo' + + def resolve(**args) + return Todo.none if user != context[:current_user] + + TodosFinder.new(user, todo_finder_params(args)).execute + end + + private + + # TODO: Support multiple queries for e.g. state and type on TodosFinder: + # + # https://gitlab.com/gitlab-org/gitlab/merge_requests/18487 + # https://gitlab.com/gitlab-org/gitlab/merge_requests/18518 + # + # As soon as these MR's are merged, we can refactor this to query by + # multiple contents. + # + def todo_finder_params(args) + { + state: first_state(args), + type: first_type(args), + group_id: first_group_id(args), + author_id: first_author_id(args), + action_id: first_action(args), + project_id: first_project(args) + } + end + + def first_project(args) + first_query_field(args, :project_id) + end + + def first_action(args) + first_query_field(args, :action) + end + + def first_author_id(args) + first_query_field(args, :author_id) + end + + def first_group_id(args) + first_query_field(args, :group_id) + end + + def first_state(args) + first_query_field(args, :state) + end + + def first_type(args) + first_query_field(args, :type) + end + + def first_query_field(query, field) + return unless query.key?(field) + + query[field].first if query[field].respond_to?(:first) + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index bbf94fb92df..996bf225976 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -14,6 +14,11 @@ module Types resolver: Resolvers::GroupResolver, description: "Find a group" + field :current_user, Types::UserType, + null: true, + resolve: -> (_obj, _args, context) { context[:current_user] }, + description: "Get information about current user" + field :namespace, Types::NamespaceType, null: true, resolver: Resolvers::NamespaceResolver, diff --git a/app/graphql/types/todo_action_enum.rb b/app/graphql/types/todo_action_enum.rb new file mode 100644 index 00000000000..0e538838474 --- /dev/null +++ b/app/graphql/types/todo_action_enum.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + class TodoActionEnum < BaseEnum + value 'assigned', value: 1 + value 'mentioned', value: 2 + value 'build_failed', value: 3 + value 'marked', value: 4 + value 'approval_required', value: 5 + value 'unmergeable', value: 6 + value 'directly_addressed', value: 7 + end +end diff --git a/app/graphql/types/todo_state_enum.rb b/app/graphql/types/todo_state_enum.rb new file mode 100644 index 00000000000..29a28b5208d --- /dev/null +++ b/app/graphql/types/todo_state_enum.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Types + class TodoStateEnum < BaseEnum + value 'pending' + value 'done' + end +end diff --git a/app/graphql/types/todo_target_enum.rb b/app/graphql/types/todo_target_enum.rb new file mode 100644 index 00000000000..9a7391dcd99 --- /dev/null +++ b/app/graphql/types/todo_target_enum.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Types + class TodoTargetEnum < BaseEnum + value 'Issue' + value 'MergeRequest' + value 'Epic' + end +end diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb new file mode 100644 index 00000000000..d36daaf7dec --- /dev/null +++ b/app/graphql/types/todo_type.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Types + class TodoType < BaseObject + graphql_name 'Todo' + description 'Representing a todo entry' + + present_using TodoPresenter + + authorize :read_todo + + field :id, GraphQL::ID_TYPE, + description: 'Id of the todo', + null: false + + field :project, Types::ProjectType, + description: 'The project this todo is associated with', + null: true, + authorize: :read_project, + resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, todo.project_id).find } + + field :group, Types::GroupType, + description: 'Group this todo is associated with', + null: true, + authorize: :read_group, + resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find } + + field :author, Types::UserType, + description: 'The owner of this todo', + null: false, + resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find } + + field :action, Types::TodoActionEnum, + description: 'Action of the todo', + null: false + + field :target_type, Types::TodoTargetEnum, + description: 'Target type of the todo', + null: false + + field :body, GraphQL::STRING_TYPE, + description: 'Body of the todo', + null: false + + field :state, Types::TodoStateEnum, + description: 'State of the todo', + null: false + + field :created_at, Types::TimeType, + description: 'Timestamp this todo was created', + null: false + end +end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 9f7d2a171d6..1ba37927b40 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -12,5 +12,8 @@ module Types field :username, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :avatar_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :todos, Types::TodoType.connection_type, null: false, + resolver: Resolvers::TodoResolver, + description: 'Todos of this user' end end diff --git a/app/models/ci/build_trace.rb b/app/models/ci/build_trace.rb new file mode 100644 index 00000000000..b9db1559836 --- /dev/null +++ b/app/models/ci/build_trace.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Ci + class BuildTrace + CONVERTERS = { + html: Gitlab::Ci::Ansi2html, + json: Gitlab::Ci::Ansi2json + }.freeze + + attr_reader :trace, :build + + delegate :state, :append, :truncated, :offset, :size, :total, to: :trace, allow_nil: true + delegate :id, :status, :complete?, to: :build, prefix: true + + def initialize(build:, stream:, state:, content_format:) + @build = build + @content_format = content_format + + if stream.valid? + stream.limit + @trace = CONVERTERS.fetch(content_format).convert(stream.stream, state) + end + end + + def json? + @content_format == :json + end + + def html? + @content_format == :html + end + + def json_lines + @trace&.lines if json? + end + + def html_lines + @trace&.html if html? + end + end +end diff --git a/app/models/evidence.rb b/app/models/evidence.rb new file mode 100644 index 00000000000..69a00f1cb3f --- /dev/null +++ b/app/models/evidence.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Evidence < ApplicationRecord + include ShaAttribute + + belongs_to :release + + before_validation :generate_summary_and_sha + + default_scope { order(created_at: :asc) } + + sha_attribute :summary_sha + + def milestones + @milestones ||= release.milestones.includes(:issues) + end + + private + + def generate_summary_and_sha + summary = Evidences::EvidenceSerializer.new.represent(self) # rubocop: disable CodeReuse/Serializer + return unless summary + + self.summary = summary + self.summary_sha = Gitlab::CryptoHelper.sha256(summary) + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 8b21206fccf..042201ffa14 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -473,6 +473,12 @@ class Group < Namespace errors.add(:visibility_level, "#{visibility} is not allowed since there are sub-groups with higher visibility.") end + + def self.groups_including_descendants_by(group_ids) + Gitlab::ObjectHierarchy + .new(Group.where(id: group_ids)) + .base_and_descendants + end end Group.prepend_if_ee('EE::Group') diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 16fc7fdbd48..e51b1c41059 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -13,7 +13,7 @@ class WebHook < ApplicationRecord algorithm: 'aes-256-gcm', key: Settings.attr_encrypted_db_key_base_32 - has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :web_hook_logs validates :url, presence: true validates :url, public_url: true, unless: ->(hook) { hook.is_a?(SystemHook) } diff --git a/app/models/release.rb b/app/models/release.rb index 8759e38060c..add57367f61 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -14,6 +14,7 @@ class Release < ApplicationRecord has_many :milestone_releases has_many :milestones, through: :milestone_releases + has_one :evidence default_value_for :released_at, allows_nil: false do Time.zone.now @@ -28,6 +29,8 @@ class Release < ApplicationRecord delegate :repository, to: :project + after_commit :create_evidence!, on: :create + def commit strong_memoize(:commit) do repository.commit(actual_sha) @@ -66,6 +69,10 @@ class Release < ApplicationRecord repository.find_tag(tag) end end + + def create_evidence! + CreateEvidenceWorker.perform_async(self.id) + end end Release.prepend_if_ee('EE::Release') diff --git a/app/models/todo.rb b/app/models/todo.rb index 456115872d1..1927b54510e 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -75,13 +75,13 @@ class Todo < ApplicationRecord after_save :keep_around_commit, if: :commit_id class << self - # Returns all todos for the given group and its descendants. + # Returns all todos for the given group ids and their descendants. # - # group - A `Group` to retrieve todos for. + # group_ids - Group Ids to retrieve todos for. # # Returns an `ActiveRecord::Relation`. - def for_group_and_descendants(group) - groups = group.self_and_descendants + def for_group_ids_and_descendants(group_ids) + groups = Group.groups_including_descendants_by(group_ids) from_union([ for_project(Project.for_group(groups)), diff --git a/app/policies/todo_policy.rb b/app/policies/todo_policy.rb new file mode 100644 index 00000000000..f8644217f04 --- /dev/null +++ b/app/policies/todo_policy.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class TodoPolicy < BasePolicy + desc 'User can only read own todos' + condition(:own_todo) do + @user && @subject.user_id == @user.id + end + + rule { own_todo }.enable :read_todo +end diff --git a/app/presenters/todo_presenter.rb b/app/presenters/todo_presenter.rb new file mode 100644 index 00000000000..b57fc712c5a --- /dev/null +++ b/app/presenters/todo_presenter.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class TodoPresenter < Gitlab::View::Presenter::Delegated + include GlobalID::Identification + + presents :todo +end diff --git a/app/serializers/build_trace_entity.rb b/app/serializers/build_trace_entity.rb new file mode 100644 index 00000000000..b5bac8a5d64 --- /dev/null +++ b/app/serializers/build_trace_entity.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class BuildTraceEntity < Grape::Entity + expose :build_id, as: :id + expose :build_status, as: :status + expose :build_complete?, as: :complete + + expose :state + expose :append + expose :truncated + expose :offset + expose :size + expose :total + + expose :json_lines, as: :lines, if: ->(*) { object.json? } + expose :html_lines, as: :html, if: ->(*) { object.html? } +end diff --git a/app/serializers/build_trace_serializer.rb b/app/serializers/build_trace_serializer.rb new file mode 100644 index 00000000000..c95158f10a4 --- /dev/null +++ b/app/serializers/build_trace_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class BuildTraceSerializer < BaseSerializer + entity BuildTraceEntity +end diff --git a/app/serializers/evidences/author_entity.rb b/app/serializers/evidences/author_entity.rb deleted file mode 100644 index 9023c64dad2..00000000000 --- a/app/serializers/evidences/author_entity.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Evidences - class AuthorEntity < Grape::Entity - expose :id - expose :name - expose :email - end -end diff --git a/app/serializers/evidences/evidence_entity.rb b/app/serializers/evidences/evidence_entity.rb new file mode 100644 index 00000000000..9689ae10895 --- /dev/null +++ b/app/serializers/evidences/evidence_entity.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Evidences + class EvidenceEntity < Grape::Entity + expose :release, using: Evidences::ReleaseEntity + end +end diff --git a/app/serializers/evidences/evidence_serializer.rb b/app/serializers/evidences/evidence_serializer.rb new file mode 100644 index 00000000000..d03032bc65c --- /dev/null +++ b/app/serializers/evidences/evidence_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Evidences + class EvidenceSerializer < BaseSerializer + entity EvidenceEntity + end +end diff --git a/app/serializers/evidences/issue_entity.rb b/app/serializers/evidences/issue_entity.rb index 883256bf38a..2f1f5dc3d18 100644 --- a/app/serializers/evidences/issue_entity.rb +++ b/app/serializers/evidences/issue_entity.rb @@ -5,7 +5,6 @@ module Evidences expose :id expose :title expose :description - expose :author, using: AuthorEntity expose :state expose :iid expose :confidential diff --git a/app/serializers/evidences/milestone_entity.rb b/app/serializers/evidences/milestone_entity.rb index 8118cab4403..eeb3d58d4c7 100644 --- a/app/serializers/evidences/milestone_entity.rb +++ b/app/serializers/evidences/milestone_entity.rb @@ -9,6 +9,6 @@ module Evidences expose :iid expose :created_at expose :due_date - expose :issues, using: IssueEntity + expose :issues, using: Evidences::IssueEntity end end diff --git a/app/serializers/evidences/release_entity.rb b/app/serializers/evidences/release_entity.rb index 8916ce67b4c..59e379a3c08 100644 --- a/app/serializers/evidences/release_entity.rb +++ b/app/serializers/evidences/release_entity.rb @@ -7,7 +7,7 @@ module Evidences expose :name expose :description expose :created_at - expose :project, using: ProjectEntity - expose :milestones, using: MilestoneEntity + expose :project, using: Evidences::ProjectEntity + expose :milestones, using: Evidences::MilestoneEntity end end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index a33afd436b0..cd8d1d05d8b 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -173,3 +173,4 @@ - delete_stored_files - import_issues_csv - project_daily_statistics +- create_evidence diff --git a/app/workers/create_evidence_worker.rb b/app/workers/create_evidence_worker.rb new file mode 100644 index 00000000000..5fc901ae514 --- /dev/null +++ b/app/workers/create_evidence_worker.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CreateEvidenceWorker + include ApplicationWorker + + def perform(release_id) + release = Release.find_by_id(release_id) + return unless release + + Evidence.create!(release: release) + end +end |