Commit 9a4c9afb authored by Sean Carroll's avatar Sean Carroll
parent 056ee1bf
# frozen_string_literal: true
module Types
class EvidenceType < BaseObject
graphql_name 'ReleaseEvidence'
description 'Evidence for a release'
authorize :download_code
present_using Releases::EvidencePresenter
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the evidence'
field :sha, GraphQL::STRING_TYPE, null: true,
description: 'SHA1 ID of the evidence hash'
field :filepath, GraphQL::STRING_TYPE, null: true,
description: 'URL from where the evidence can be downloaded'
field :collected_at, Types::TimeType, null: true,
description: 'Timestamp when the evidence was collected'
end
end
......@@ -27,6 +27,8 @@ module Types
description: 'Assets of the release'
field :milestones, Types::MilestoneType.connection_type, null: true,
description: 'Milestones associated to the release'
field :evidences, Types::EvidenceType.connection_type, null: true,
description: 'Evidence for the release'
field :author, Types::UserType, null: true,
description: 'User that created the release'
......
# frozen_string_literal: true
class Releases::Evidence < ApplicationRecord
include ShaAttribute
include Presentable
module Releases
class Evidence < ApplicationRecord
include ShaAttribute
include Presentable
belongs_to :release, inverse_of: :evidences
belongs_to :release, inverse_of: :evidences
default_scope { order(created_at: :asc) }
default_scope { order(created_at: :asc) }
sha_attribute :summary_sha
alias_attribute :collected_at, :created_at
sha_attribute :summary_sha
alias_attribute :collected_at, :created_at
alias_attribute :sha, :summary_sha
def milestones
@milestones ||= release.milestones.includes(:issues)
end
def milestones
@milestones ||= release.milestones.includes(:issues)
end
##
# Return `summary` without sensitive information.
#
# Removing issues from summary in order to prevent leaking confidential ones.
# See more https://gitlab.com/gitlab-org/gitlab/issues/121930
def summary
safe_summary = read_attribute(:summary)
##
# Return `summary` without sensitive information.
#
# Removing issues from summary in order to prevent leaking confidential ones.
# See more https://gitlab.com/gitlab-org/gitlab/issues/121930
def summary
safe_summary = read_attribute(:summary)
safe_summary.dig('release', 'milestones')&.each do |milestone|
milestone.delete('issues')
end
safe_summary.dig('release', 'milestones')&.each do |milestone|
milestone.delete('issues')
end
safe_summary
safe_summary
end
end
end
---
title: Add Evidence to Releases GraphQL endpoint
merge_request: 33254
author:
type: added
......@@ -9971,6 +9971,31 @@ type Release {
"""
descriptionHtml: String
"""
Evidence for the release
"""
evidences(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): ReleaseEvidenceConnection
"""
Milestones associated to the release
"""
......@@ -10109,6 +10134,66 @@ type ReleaseEdge {
node: Release
}
"""
Evidence for a release
"""
type ReleaseEvidence {
"""
Timestamp when the evidence was collected
"""
collectedAt: Time
"""
URL from where the evidence can be downloaded
"""
filepath: String
"""
ID of the evidence
"""
id: ID!
"""
SHA1 ID of the evidence hash
"""
sha: String
}
"""
The connection type for ReleaseEvidence.
"""
type ReleaseEvidenceConnection {
"""
A list of edges.
"""
edges: [ReleaseEvidenceEdge]
"""
A list of nodes.
"""
nodes: [ReleaseEvidence]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type ReleaseEvidenceEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: ReleaseEvidence
}
type ReleaseLink {
"""
Indicates the link points to an external resource
......
......@@ -29243,6 +29243,59 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "evidences",
"description": "Evidence for the release",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "ReleaseEvidenceConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "milestones",
"description": "Milestones associated to the release",
......@@ -29609,6 +29662,191 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ReleaseEvidence",
"description": "Evidence for a release",
"fields": [
{
"name": "collectedAt",
"description": "Timestamp when the evidence was collected",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "filepath",
"description": "URL from where the evidence can be downloaded",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the evidence",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sha",
"description": "SHA1 ID of the evidence hash",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ReleaseEvidenceConnection",
"description": "The connection type for ReleaseEvidence.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ReleaseEvidenceEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ReleaseEvidence",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ReleaseEvidenceEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "ReleaseEvidence",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ReleaseLink",
......@@ -1406,6 +1406,17 @@ Represents a Project Member
| --- | ---- | ---------- |
| `assetsCount` | Int | Number of assets of the release |
## ReleaseEvidence
Evidence for a release
| Name | Type | Description |
| --- | ---- | ---------- |
| `collectedAt` | Time | Timestamp when the evidence was collected |
| `filepath` | String | URL from where the evidence can be downloaded |
| `id` | ID! | ID of the evidence |
| `sha` | String | SHA1 ID of the evidence hash |
## ReleaseLink
| Name | Type | Description |
......
......@@ -6,7 +6,7 @@ module API
class Evidence < Grape::Entity
include ::API::Helpers::Presentable
expose :summary_sha, as: :sha
expose :sha
expose :filepath
expose :collected_at
end
......
......@@ -3,5 +3,7 @@
FactoryBot.define do
factory :evidence, class: 'Releases::Evidence' do
release
summary_sha { "760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d" }
summary { { "release": { "tag": "v4.0", "name": "New release", "project_name": "Project name" } } }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['ReleaseEvidence'] do
it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
expected_fields = %w[
id sha filepath collected_at
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
......@@ -9,7 +9,7 @@ describe GitlabSchema.types['Release'] do
expected_fields = %w[
tag_name tag_path
description description_html
name assets milestones author commit
name assets milestones evidences author commit
created_at released_at
]
......@@ -28,6 +28,12 @@ describe GitlabSchema.types['Release'] do
it { is_expected.to have_graphql_type(Types::MilestoneType.connection_type) }
end
describe 'evidences field' do
subject { described_class.fields['evidences'] }
it { is_expected.to have_graphql_type(Types::EvidenceType.connection_type) }
end
describe 'author field' do
subject { described_class.fields['author'] }
......
......@@ -5,11 +5,12 @@ require 'pp'
describe 'Query.project(fullPath).release(tagName)' do
include GraphqlHelpers
include Presentable
let_it_be(:project) { create(:project, :repository) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
let_it_be(:release) { create(:release, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
let_it_be(:release_link_2) { create(:release_link, release: release) }
let_it_be(:developer) { create(:user) }
......@@ -164,5 +165,42 @@ describe 'Query.project(fullPath).release(tagName)' do
expect(data).to match_array(expected)
end
end
describe 'evidences' do
let(:path) { path_prefix + %w[evidences] }
let(:release_fields) do
query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
end
context 'for a developer' do
it 'finds all evidence fields' do
post_query
evidence = release.evidences.first.present
expected = {
'id' => global_id_of(evidence),
'sha' => evidence.sha,
'filepath' => evidence.filepath,
'collectedAt' => evidence.collected_at.utc.iso8601
}
expect(data["nodes"].first).to eq(expected)
end
end
context 'for a guest' do
let(:current_user) { create :user }
before do
project.add_guest(current_user)
end
it 'denies access' do
post_query
expect(data['node']).to be_nil
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment