Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b3db40398c
commit
17b91a3c6a
|
|
@ -1,4 +1,6 @@
|
|||
import { join as joinPaths } from 'path';
|
||||
const PATH_SEPARATOR = '/';
|
||||
const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`);
|
||||
const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`);
|
||||
|
||||
// Returns a decoded url parameter value
|
||||
// - Treats '+' as '%20'
|
||||
|
|
@ -6,6 +8,37 @@ function decodeUrlParameter(val) {
|
|||
return decodeURIComponent(val.replace(/\+/g, '%20'));
|
||||
}
|
||||
|
||||
function cleanLeadingSeparator(path) {
|
||||
return path.replace(PATH_SEPARATOR_LEADING_REGEX, '');
|
||||
}
|
||||
|
||||
function cleanEndingSeparator(path) {
|
||||
return path.replace(PATH_SEPARATOR_ENDING_REGEX, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely joins the given paths which might both start and end with a `/`
|
||||
*
|
||||
* Example:
|
||||
* - `joinPaths('abc/', '/def') === 'abc/def'`
|
||||
* - `joinPaths(null, 'abc/def', 'zoo) === 'abc/def/zoo'`
|
||||
*
|
||||
* @param {...String} paths
|
||||
* @returns {String}
|
||||
*/
|
||||
export function joinPaths(...paths) {
|
||||
return paths.reduce((acc, path) => {
|
||||
if (!path) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return [cleanEndingSeparator(acc), PATH_SEPARATOR, cleanLeadingSeparator(path)].join('');
|
||||
}, '');
|
||||
}
|
||||
|
||||
// Returns an array containing the value(s) of the
|
||||
// of the key passed as an argument
|
||||
export function getParameterValues(sParam, url = window.location) {
|
||||
|
|
@ -212,5 +245,3 @@ export function objectToQuery(obj) {
|
|||
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export { joinPaths };
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
|
||||
before_action :verify_api_request!, only: :terminal_websocket_authorize
|
||||
before_action only: [:show] do
|
||||
push_frontend_feature_flag(:job_log_json, project)
|
||||
push_frontend_feature_flag(:job_log_json, project, default_enabled: true)
|
||||
end
|
||||
|
||||
layout 'project'
|
||||
|
|
@ -53,7 +53,7 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
format.json do
|
||||
# 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
|
||||
content_format = Feature.enabled?(:job_log_json, @project, default_enabled: true) ? :json : :html
|
||||
|
||||
build_trace = Ci::BuildTrace.new(
|
||||
build: @build,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Snippets
|
||||
class Base < BaseMutation
|
||||
field :snippet,
|
||||
Types::SnippetType,
|
||||
null: true,
|
||||
description: 'The snippet after mutation'
|
||||
|
||||
private
|
||||
|
||||
def find_object(id:)
|
||||
GitlabSchema.object_from_id(id)
|
||||
end
|
||||
|
||||
def authorized_resource?(snippet)
|
||||
Ability.allowed?(context[:current_user], ability_for(snippet), snippet)
|
||||
end
|
||||
|
||||
def ability_for(snippet)
|
||||
"#{ability_name}_#{snippet.to_ability_name}".to_sym
|
||||
end
|
||||
|
||||
def ability_name
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Snippets
|
||||
class Create < BaseMutation
|
||||
include Mutations::ResolvesProject
|
||||
|
||||
graphql_name 'CreateSnippet'
|
||||
|
||||
field :snippet,
|
||||
Types::SnippetType,
|
||||
null: true,
|
||||
description: 'The snippet after mutation'
|
||||
|
||||
argument :title, GraphQL::STRING_TYPE,
|
||||
required: true,
|
||||
description: 'Title of the snippet'
|
||||
|
||||
argument :file_name, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'File name of the snippet'
|
||||
|
||||
argument :content, GraphQL::STRING_TYPE,
|
||||
required: true,
|
||||
description: 'Content of the snippet'
|
||||
|
||||
argument :description, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'Description of the snippet'
|
||||
|
||||
argument :visibility_level, Types::VisibilityLevelsEnum,
|
||||
description: 'The visibility level of the snippet',
|
||||
required: true
|
||||
|
||||
argument :project_path, GraphQL::ID_TYPE,
|
||||
required: false,
|
||||
description: 'The project full path the snippet is associated with'
|
||||
|
||||
def resolve(args)
|
||||
project_path = args.delete(:project_path)
|
||||
|
||||
if project_path.present?
|
||||
project = find_project!(project_path: project_path)
|
||||
elsif !can_create_personal_snippet?
|
||||
raise_resource_not_avaiable_error!
|
||||
end
|
||||
|
||||
snippet = CreateSnippetService.new(project,
|
||||
context[:current_user],
|
||||
args).execute
|
||||
|
||||
{
|
||||
snippet: snippet.valid? ? snippet : nil,
|
||||
errors: errors_on_object(snippet)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_project!(project_path:)
|
||||
authorized_find!(full_path: project_path)
|
||||
end
|
||||
|
||||
def find_object(full_path:)
|
||||
resolve_project(full_path: full_path)
|
||||
end
|
||||
|
||||
def authorized_resource?(project)
|
||||
Ability.allowed?(context[:current_user], :create_project_snippet, project)
|
||||
end
|
||||
|
||||
def can_create_personal_snippet?
|
||||
Ability.allowed?(context[:current_user], :create_personal_snippet)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Snippets
|
||||
class Destroy < Base
|
||||
graphql_name 'DestroySnippet'
|
||||
|
||||
ERROR_MSG = 'Error deleting the snippet'
|
||||
|
||||
argument :id,
|
||||
GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: 'The global id of the snippet to destroy'
|
||||
|
||||
def resolve(id:)
|
||||
snippet = authorized_find!(id: id)
|
||||
|
||||
result = snippet.destroy
|
||||
errors = result ? [] : [ERROR_MSG]
|
||||
|
||||
{
|
||||
errors: errors
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ability_name
|
||||
"admin"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Snippets
|
||||
class Update < Base
|
||||
graphql_name 'UpdateSnippet'
|
||||
|
||||
argument :id,
|
||||
GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: 'The global id of the snippet to update'
|
||||
|
||||
argument :title, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'Title of the snippet'
|
||||
|
||||
argument :file_name, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'File name of the snippet'
|
||||
|
||||
argument :content, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'Content of the snippet'
|
||||
|
||||
argument :description, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'Description of the snippet'
|
||||
|
||||
argument :visibility_level, Types::VisibilityLevelsEnum,
|
||||
description: 'The visibility level of the snippet',
|
||||
required: false
|
||||
|
||||
def resolve(args)
|
||||
snippet = authorized_find!(id: args.delete(:id))
|
||||
|
||||
result = UpdateSnippetService.new(snippet.project,
|
||||
context[:current_user],
|
||||
snippet,
|
||||
args).execute
|
||||
|
||||
{
|
||||
snippet: result ? snippet : snippet.reset,
|
||||
errors: errors_on_object(snippet)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ability_name
|
||||
"update"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Resolvers
|
||||
class BaseResolver < GraphQL::Schema::Resolver
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
def self.single
|
||||
@single ||= Class.new(self) do
|
||||
def resolve(**args)
|
||||
|
|
@ -36,5 +38,13 @@ module Resolvers
|
|||
# complexity difference is minimal in this case.
|
||||
[args[:iid], args[:iids]].any? ? 0 : 0.01
|
||||
end
|
||||
|
||||
override :object
|
||||
def object
|
||||
super.tap do |obj|
|
||||
# If the field this resolver is used in is wrapped in a presenter, go back to it's subject
|
||||
break obj.subject if obj.is_a?(Gitlab::View::Presenter::Base)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ module Types
|
|||
mount_mutation Mutations::Todos::MarkDone
|
||||
mount_mutation Mutations::Todos::Restore
|
||||
mount_mutation Mutations::Todos::MarkAllDone
|
||||
mount_mutation Mutations::Snippets::Destroy
|
||||
mount_mutation Mutations::Snippets::Update
|
||||
mount_mutation Mutations::Snippets::Create
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ module Types
|
|||
description: 'Description of the snippet',
|
||||
null: true
|
||||
|
||||
field :visibility, GraphQL::STRING_TYPE,
|
||||
description: 'Visibility of the snippet',
|
||||
field :visibility_level, Types::VisibilityLevelsEnum,
|
||||
description: 'Visibility Level of the snippet',
|
||||
null: false
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class VisibilityLevelsEnum < BaseEnum
|
||||
Gitlab::VisibilityLevel.string_options.each do |name, int_value|
|
||||
value name.downcase, value: int_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -30,6 +30,6 @@ class SnippetPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def ability_name(ability_prefix)
|
||||
"#{ability_prefix}_#{snippet.class.underscore}".to_sym
|
||||
"#{ability_prefix}_#{snippet.to_ability_name}".to_sym
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
%br
|
||||
|
||||
%div
|
||||
%span= _('Each Runner can be in one of the following states:')
|
||||
%span= _('Each Runner can be in one of the following states and/or belong to one of the following types:')
|
||||
%ul
|
||||
%li
|
||||
%span.badge.badge-success shared
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
.runners-content.content-list
|
||||
.table-holder
|
||||
.gl-responsive-table-row.table-row-header{ role: 'row' }
|
||||
.table-section.section-10{ role: 'rowheader' }= _('Type')
|
||||
.table-section.section-10{ role: 'rowheader' }= _('Type/State')
|
||||
.table-section.section-10{ role: 'rowheader' }= _('Runner token')
|
||||
.table-section.section-20{ role: 'rowheader' }= _('Description')
|
||||
.table-section.section-10{ role: 'rowheader' }= _('Version')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixes wording on runner admin
|
||||
merge_request:
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Added Snippets GraphQL mutations
|
||||
merge_request: 20956
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable new job log by default
|
||||
merge_request: 21543
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -442,6 +442,66 @@ type CreateNotePayload {
|
|||
note: Note
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of CreateSnippet
|
||||
"""
|
||||
input CreateSnippetInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Content of the snippet
|
||||
"""
|
||||
content: String!
|
||||
|
||||
"""
|
||||
Description of the snippet
|
||||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
File name of the snippet
|
||||
"""
|
||||
fileName: String
|
||||
|
||||
"""
|
||||
The project full path the snippet is associated with
|
||||
"""
|
||||
projectPath: ID
|
||||
|
||||
"""
|
||||
Title of the snippet
|
||||
"""
|
||||
title: String!
|
||||
|
||||
"""
|
||||
The visibility level of the snippet
|
||||
"""
|
||||
visibilityLevel: VisibilityLevelsEnum!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of CreateSnippet
|
||||
"""
|
||||
type CreateSnippetPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Reasons why the mutation failed.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The snippet after mutation
|
||||
"""
|
||||
snippet: Snippet
|
||||
}
|
||||
|
||||
type Design implements Noteable {
|
||||
diffRefs: DiffRefs!
|
||||
|
||||
|
|
@ -861,6 +921,41 @@ type DestroyNotePayload {
|
|||
note: Note
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DestroySnippet
|
||||
"""
|
||||
input DestroySnippetInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The global id of the snippet to destroy
|
||||
"""
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DestroySnippet
|
||||
"""
|
||||
type DestroySnippetPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Reasons why the mutation failed.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The snippet after mutation
|
||||
"""
|
||||
snippet: Snippet
|
||||
}
|
||||
|
||||
type DetailedStatus {
|
||||
detailsPath: String!
|
||||
favicon: String!
|
||||
|
|
@ -3737,9 +3832,11 @@ type Mutation {
|
|||
createEpic(input: CreateEpicInput!): CreateEpicPayload
|
||||
createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload
|
||||
createNote(input: CreateNoteInput!): CreateNotePayload
|
||||
createSnippet(input: CreateSnippetInput!): CreateSnippetPayload
|
||||
designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload
|
||||
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
|
||||
destroyNote(input: DestroyNoteInput!): DestroyNotePayload
|
||||
destroySnippet(input: DestroySnippetInput!): DestroySnippetPayload
|
||||
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
|
||||
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
|
||||
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
|
||||
|
|
@ -3757,6 +3854,7 @@ type Mutation {
|
|||
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload
|
||||
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
|
||||
updateNote(input: UpdateNoteInput!): UpdateNotePayload
|
||||
updateSnippet(input: UpdateSnippetInput!): UpdateSnippetPayload
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
@ -5396,9 +5494,9 @@ type Snippet implements Noteable {
|
|||
userPermissions: SnippetPermissions!
|
||||
|
||||
"""
|
||||
Visibility of the snippet
|
||||
Visibility Level of the snippet
|
||||
"""
|
||||
visibility: String!
|
||||
visibilityLevel: VisibilityLevelsEnum!
|
||||
|
||||
"""
|
||||
Web URL of the snippet
|
||||
|
|
@ -6120,6 +6218,66 @@ type UpdateNotePayload {
|
|||
note: Note
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of UpdateSnippet
|
||||
"""
|
||||
input UpdateSnippetInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Content of the snippet
|
||||
"""
|
||||
content: String
|
||||
|
||||
"""
|
||||
Description of the snippet
|
||||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
File name of the snippet
|
||||
"""
|
||||
fileName: String
|
||||
|
||||
"""
|
||||
The global id of the snippet to update
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Title of the snippet
|
||||
"""
|
||||
title: String
|
||||
|
||||
"""
|
||||
The visibility level of the snippet
|
||||
"""
|
||||
visibilityLevel: VisibilityLevelsEnum
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of UpdateSnippet
|
||||
"""
|
||||
type UpdateSnippetPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Reasons why the mutation failed.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The snippet after mutation
|
||||
"""
|
||||
snippet: Snippet
|
||||
}
|
||||
|
||||
scalar Upload
|
||||
|
||||
type User {
|
||||
|
|
@ -6286,6 +6444,12 @@ type UserPermissions {
|
|||
createSnippet: Boolean!
|
||||
}
|
||||
|
||||
enum VisibilityLevelsEnum {
|
||||
internal
|
||||
private
|
||||
public
|
||||
}
|
||||
|
||||
enum VisibilityScopesEnum {
|
||||
internal
|
||||
private
|
||||
|
|
|
|||
|
|
@ -6442,8 +6442,8 @@
|
|||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "visibility",
|
||||
"description": "Visibility of the snippet",
|
||||
"name": "visibilityLevel",
|
||||
"description": "Visibility Level of the snippet",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
@ -6451,8 +6451,8 @@
|
|||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"kind": "ENUM",
|
||||
"name": "VisibilityLevelsEnum",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
|
@ -6828,6 +6828,35 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "VisibilityLevelsEnum",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "private",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "internal",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "public",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "VisibilityScopesEnum",
|
||||
|
|
@ -15804,6 +15833,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createSnippet",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateSnippetInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateSnippetPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "designManagementDelete",
|
||||
"description": null,
|
||||
|
|
@ -15885,6 +15941,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "destroySnippet",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DestroySnippetInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DestroySnippetPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "epicSetSubscription",
|
||||
"description": null,
|
||||
|
|
@ -16343,6 +16426,33 @@
|
|||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updateSnippet",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSnippetInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "UpdateSnippetPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
|
@ -19093,6 +19203,420 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DestroySnippetPayload",
|
||||
"description": "Autogenerated return type of DestroySnippet",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Reasons why the mutation failed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "snippet",
|
||||
"description": "The snippet after mutation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Snippet",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DestroySnippetInput",
|
||||
"description": "Autogenerated input type of DestroySnippet",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The global id of the snippet to destroy",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "UpdateSnippetPayload",
|
||||
"description": "Autogenerated return type of UpdateSnippet",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Reasons why the mutation failed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "snippet",
|
||||
"description": "The snippet after mutation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Snippet",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSnippetInput",
|
||||
"description": "Autogenerated input type of UpdateSnippet",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The global id of the snippet to update",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"description": "Title of the snippet",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "fileName",
|
||||
"description": "File name of the snippet",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"description": "Content of the snippet",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the snippet",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "visibilityLevel",
|
||||
"description": "The visibility level of the snippet",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "VisibilityLevelsEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateSnippetPayload",
|
||||
"description": "Autogenerated return type of CreateSnippet",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Reasons why the mutation failed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "snippet",
|
||||
"description": "The snippet after mutation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Snippet",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateSnippetInput",
|
||||
"description": "Autogenerated input type of CreateSnippet",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "title",
|
||||
"description": "Title of the snippet",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "fileName",
|
||||
"description": "File name of the snippet",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"description": "Content of the snippet",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the snippet",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "visibilityLevel",
|
||||
"description": "The visibility level of the snippet",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "VisibilityLevelsEnum",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "projectPath",
|
||||
"description": "The project full path the snippet is associated with",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DesignManagementUploadPayload",
|
||||
|
|
|
|||
|
|
@ -92,6 +92,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `note` | Note | The note after mutation |
|
||||
|
||||
### CreateSnippetPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `snippet` | Snippet | The snippet after mutation |
|
||||
|
||||
### Design
|
||||
|
||||
| Name | Type | Description |
|
||||
|
|
@ -145,6 +153,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `note` | Note | The note after mutation |
|
||||
|
||||
### DestroySnippetPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `snippet` | Snippet | The snippet after mutation |
|
||||
|
||||
### DetailedStatus
|
||||
|
||||
| Name | Type | Description |
|
||||
|
|
@ -802,7 +818,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `fileName` | String | File Name of the snippet |
|
||||
| `content` | String! | Content of the snippet |
|
||||
| `description` | String | Description of the snippet |
|
||||
| `visibility` | String! | Visibility of the snippet |
|
||||
| `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet |
|
||||
| `createdAt` | Time! | Timestamp this snippet was created |
|
||||
| `updatedAt` | Time! | Timestamp this snippet was updated |
|
||||
| `webUrl` | String! | Web URL of the snippet |
|
||||
|
|
@ -929,6 +945,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `note` | Note | The note after mutation |
|
||||
|
||||
### UpdateSnippetPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `snippet` | Snippet | The snippet after mutation |
|
||||
|
||||
### User
|
||||
|
||||
| Name | Type | Description |
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 272 KiB |
|
|
@ -149,12 +149,13 @@ The union of A, B, and C is (1, 4) and (6, 7). Therefore, the total running time
|
|||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/14664) in GitLab
|
||||
> 12.0.
|
||||
|
||||
Job logs are divided into sections that can be collapsed or expanded.
|
||||
Job logs are divided into sections that can be collapsed or expanded. Each section will display
|
||||
the duration.
|
||||
|
||||
In the following example:
|
||||
|
||||
- Two sections are expanded and can be collapsed.
|
||||
- One section is collapsed and can be expanded.
|
||||
- Two sections are collapsed and can be expanded.
|
||||
- Three sections are expanded and can be collapsed.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -76,7 +76,7 @@ To the right of the filters, you should see a **Hide dismissed** toggle button.
|
|||
NOTE: **Note:**
|
||||
The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
|
||||
|
||||

|
||||

|
||||
|
||||
Selecting one or more filters will filter the results in this page. Disabling the **Hide dismissed**
|
||||
toggle button will let you also see vulnerabilities that have been dismissed.
|
||||
|
|
@ -97,6 +97,17 @@ vulnerabilities your projects had at various points in time. You can filter amon
|
|||
90 days, with the default being 90. Hover over the chart to get more details about
|
||||
the open vulnerabilities at a specific time.
|
||||
|
||||
Below the timeline chart is a list of projects, grouped and sorted by the severity of the vulnerability found:
|
||||
|
||||
- F: 1 or more "critical"
|
||||
- D: 1 or more "high" or "unknown"
|
||||
- C: 1 or more "medium"
|
||||
- B: 1 or more "low"
|
||||
- A: 0 vulnerabilities
|
||||
|
||||
Projects with no vulnerability tests configured will not appear in the list. Additionally, dismissed
|
||||
vulnerabilities are not included either.
|
||||
|
||||
Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
|
||||
|
||||
## Keeping the dashboards up to date
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ module Gitlab
|
|||
module AuthorizeResource
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
RESOURCE_ACCESS_ERROR = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
|
||||
|
||||
class_methods do
|
||||
def required_permissions
|
||||
# If the `#authorize` call is used on multiple classes, we add the
|
||||
|
|
@ -38,8 +40,7 @@ module Gitlab
|
|||
|
||||
def authorize!(object)
|
||||
unless authorized_resource?(object)
|
||||
raise Gitlab::Graphql::Errors::ResourceNotAvailable,
|
||||
"The resource that you are attempting to access does not exist or you don't have permission to perform this action"
|
||||
raise_resource_not_avaiable_error!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -61,6 +62,10 @@ module Gitlab
|
|||
Ability.allowed?(current_user, ability, object, scope: :user)
|
||||
end
|
||||
end
|
||||
|
||||
def raise_resource_not_avaiable_error!
|
||||
raise Gitlab::Graphql::Errors::ResourceNotAvailable, RESOURCE_ACCESS_ERROR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -153,6 +153,11 @@ msgid_plural "%d more comments"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d project"
|
||||
msgid_plural "%d projects"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d request with warnings"
|
||||
msgid_plural "%d requests with warnings"
|
||||
msgstr[0] ""
|
||||
|
|
@ -5139,6 +5144,9 @@ msgstr ""
|
|||
msgid "Creation date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Critical vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cron Timezone"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -6225,6 +6233,9 @@ msgstr ""
|
|||
msgid "Dynamic Application Security Testing (DAST)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Each Runner can be in one of the following states and/or belong to one of the following types:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Each Runner can be in one of the following states:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9205,6 +9216,9 @@ msgstr ""
|
|||
msgid "Hiding all labels"
|
||||
msgstr ""
|
||||
|
||||
msgid "High or unknown vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10638,6 +10652,9 @@ msgstr ""
|
|||
msgid "Logs|To see the pod logs, deploy your code to an environment."
|
||||
msgstr ""
|
||||
|
||||
msgid "Low vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
msgid "MB"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10887,6 +10904,9 @@ msgstr ""
|
|||
msgid "Median"
|
||||
msgstr ""
|
||||
|
||||
msgid "Medium vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
msgid "Member lock"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11818,6 +11838,9 @@ msgstr ""
|
|||
msgid "No vulnerabilities found for this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "No vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
msgid "No, directly import the existing email addresses and usernames."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13575,6 +13598,12 @@ msgstr ""
|
|||
msgid "Project path"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project security status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project security status help page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project slug"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13944,6 +13973,9 @@ msgstr ""
|
|||
msgid "Projects Successfully Retrieved"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects are graded based on the highest severity vulnerability present"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects shared with %{group_name}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13953,6 +13985,21 @@ msgstr ""
|
|||
msgid "Projects to index"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with critical vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with high or unknown vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with low vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with medium vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with no vulnerabilities and security scanning enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with write access"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -18870,6 +18917,9 @@ msgstr ""
|
|||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type/State"
|
||||
msgstr ""
|
||||
|
||||
msgid "U2F Devices (%{length})"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -18903,6 +18953,9 @@ msgstr ""
|
|||
msgid "Unable to connect to server: %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to fetch vulnerable projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to generate new instance ID"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21756,6 +21809,9 @@ msgstr ""
|
|||
msgid "severity|Medium"
|
||||
msgstr ""
|
||||
|
||||
msgid "severity|None"
|
||||
msgstr ""
|
||||
|
||||
msgid "severity|Undefined"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
|
||||
before do
|
||||
stub_feature_flags(ci_enable_live_trace: true)
|
||||
stub_feature_flags(job_log_json: false)
|
||||
stub_not_protect_default_branch
|
||||
end
|
||||
|
||||
|
|
@ -527,7 +526,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
|
||||
describe 'GET trace.json' do
|
||||
before do
|
||||
stub_feature_flags(job_log_json: true)
|
||||
get_trace
|
||||
end
|
||||
|
||||
|
|
@ -634,6 +632,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
|
||||
describe 'GET legacy trace.json' do
|
||||
before do
|
||||
stub_feature_flags(job_log_json: false)
|
||||
get_trace
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ describe 'Project Jobs Permissions' do
|
|||
let!(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(job_log_json: true)
|
||||
sign_in(user)
|
||||
|
||||
project.enable_ci
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ describe 'User browses a job', :js do
|
|||
let!(:build) { create(:ci_build, :success, :trace_artifact, :coverage, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(job_log_json: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
project.enable_ci
|
||||
|
||||
|
|
@ -24,7 +22,7 @@ describe 'User browses a job', :js do
|
|||
wait_for_requests
|
||||
|
||||
expect(page).to have_content("Job ##{build.id}")
|
||||
expect(page).to have_css('.js-build-trace')
|
||||
expect(page).to have_css('.job-log')
|
||||
|
||||
# scroll to the top of the page first
|
||||
execute_script "window.scrollTo(0,0)"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
|
|||
before do
|
||||
project.add_role(user, user_access_level)
|
||||
sign_in(user)
|
||||
stub_feature_flags(job_log_json: false)
|
||||
end
|
||||
|
||||
describe "GET /:project/jobs" do
|
||||
|
|
@ -810,7 +809,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
|
|||
|
||||
it 'renders job log' do
|
||||
wait_for_all_requests
|
||||
expect(page).to have_selector('.js-build-trace')
|
||||
expect(page).to have_selector('.job-log')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ describe "Internal Project Access" do
|
|||
|
||||
set(:project) { create(:project, :internal, :repository) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(job_log_json: false)
|
||||
end
|
||||
|
||||
describe "Project should be internal" do
|
||||
describe '#internal?' do
|
||||
subject { project.internal? }
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ describe "Private Project Access" do
|
|||
|
||||
set(:project) { create(:project, :private, :repository, public_builds: false) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(job_log_json: false)
|
||||
end
|
||||
|
||||
describe "Project should be private" do
|
||||
describe '#private?' do
|
||||
subject { project.private? }
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ describe "Public Project Access" do
|
|||
|
||||
set(:project) { create(:project, :public, :repository) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(job_log_json: false)
|
||||
end
|
||||
|
||||
describe "Project should be public" do
|
||||
describe '#public?' do
|
||||
subject { project.public? }
|
||||
|
|
|
|||
|
|
@ -298,4 +298,28 @@ describe('URL utility', () => {
|
|||
expect(urlUtils.objectToQuery(searchQueryObject)).toEqual('one=1&two=2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('joinPaths', () => {
|
||||
it.each`
|
||||
paths | expected
|
||||
${['foo', 'bar']} | ${'foo/bar'}
|
||||
${['foo/', 'bar']} | ${'foo/bar'}
|
||||
${['foo//', 'bar']} | ${'foo/bar'}
|
||||
${['abc/', '/def']} | ${'abc/def'}
|
||||
${['foo', '/bar']} | ${'foo/bar'}
|
||||
${['foo', '/bar/']} | ${'foo/bar/'}
|
||||
${['foo', '//bar/']} | ${'foo/bar/'}
|
||||
${['foo', '', '/bar']} | ${'foo/bar'}
|
||||
${['foo', '/bar', '']} | ${'foo/bar'}
|
||||
${['/', '', 'foo/bar/ ', '', '/ninja']} | ${'/foo/bar/ /ninja'}
|
||||
${['', '/ninja', '/', ' ', '', 'bar', ' ']} | ${'/ninja/ /bar/ '}
|
||||
${['http://something/bar/', 'foo']} | ${'http://something/bar/foo'}
|
||||
${['foo/bar', null, 'ninja', null]} | ${'foo/bar/ninja'}
|
||||
${[null, 'abc/def', 'zoo']} | ${'abc/def/zoo'}
|
||||
${['', '', '']} | ${''}
|
||||
${['///', '/', '//']} | ${'/'}
|
||||
`('joins paths $paths => $expected', ({ paths, expected }) => {
|
||||
expect(urlUtils.joinPaths(...paths)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ describe Resolvers::BaseResolver do
|
|||
let(:resolver) do
|
||||
Class.new(described_class) do
|
||||
def resolve(**args)
|
||||
process(object)
|
||||
|
||||
[args, args]
|
||||
end
|
||||
|
||||
def process(obj); end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -69,4 +73,26 @@ describe Resolvers::BaseResolver do
|
|||
expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
describe '#object' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
it 'returns object' do
|
||||
expect_next_instance_of(resolver) do |r|
|
||||
expect(r).to receive(:process).with(user)
|
||||
end
|
||||
|
||||
resolve(resolver, obj: user)
|
||||
end
|
||||
|
||||
context 'when object is a presenter' do
|
||||
it 'returns presented object' do
|
||||
expect_next_instance_of(resolver) do |r|
|
||||
expect(r).to receive(:process).with(user)
|
||||
end
|
||||
|
||||
resolve(resolver, obj: UserPresenter.new(user))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ describe GitlabSchema.types['Snippet'] do
|
|||
it 'has the correct fields' do
|
||||
expected_fields = [:id, :title, :project, :author,
|
||||
:file_name, :content, :description,
|
||||
:visibility, :created_at, :updated_at,
|
||||
:visibility_level, :created_at, :updated_at,
|
||||
:web_url, :raw_url, :notes, :discussions,
|
||||
:user_permissions, :description_html]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Creating a Snippet' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:content) { 'Initial content' }
|
||||
let(:description) { 'Initial description' }
|
||||
let(:title) { 'Initial title' }
|
||||
let(:file_name) { 'Initial file_name' }
|
||||
let(:visibility_level) { 'public' }
|
||||
let(:project_path) { nil }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
content: content,
|
||||
description: description,
|
||||
visibility_level: visibility_level,
|
||||
file_name: file_name,
|
||||
title: title,
|
||||
project_path: project_path
|
||||
}
|
||||
|
||||
graphql_mutation(:create_snippet, variables)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:create_snippet)
|
||||
end
|
||||
|
||||
context 'when the user does not have permission' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it_behaves_like 'a mutation that returns top-level errors',
|
||||
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
|
||||
|
||||
it 'does not create the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.not_to change { Snippet.count }
|
||||
end
|
||||
|
||||
context 'when user is not authorized in the project' do
|
||||
let(:project_path) { project.full_path }
|
||||
|
||||
it 'does not create the snippet when the user is not authorized' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.not_to change { Snippet.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has permission' do
|
||||
let(:current_user) { user }
|
||||
|
||||
context 'with PersonalSnippet' do
|
||||
it 'creates the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change { Snippet.count }.by(1)
|
||||
end
|
||||
|
||||
it 'returns the created Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['snippet']['content']).to eq(content)
|
||||
expect(mutation_response['snippet']['title']).to eq(title)
|
||||
expect(mutation_response['snippet']['description']).to eq(description)
|
||||
expect(mutation_response['snippet']['fileName']).to eq(file_name)
|
||||
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
|
||||
expect(mutation_response['snippet']['project']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ProjectSnippet' do
|
||||
let(:project_path) { project.full_path }
|
||||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'creates the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change { Snippet.count }.by(1)
|
||||
end
|
||||
|
||||
it 'returns the created Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['snippet']['content']).to eq(content)
|
||||
expect(mutation_response['snippet']['title']).to eq(title)
|
||||
expect(mutation_response['snippet']['description']).to eq(description)
|
||||
expect(mutation_response['snippet']['fileName']).to eq(file_name)
|
||||
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
|
||||
expect(mutation_response['snippet']['project']['fullPath']).to eq(project_path)
|
||||
end
|
||||
|
||||
context 'when the project path is invalid' do
|
||||
let(:project_path) { 'foobar' }
|
||||
|
||||
it 'returns an an error' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the feature is disabled' do
|
||||
it 'returns an an error' do
|
||||
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are ActiveRecord validation errors' do
|
||||
let(:title) { '' }
|
||||
|
||||
it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"]
|
||||
|
||||
it 'does not create the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.not_to change { Snippet.count }
|
||||
end
|
||||
|
||||
it 'does not return Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['snippet']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Destroying a Snippet' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:current_user) { snippet.author }
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
id: snippet.to_global_id.to_s
|
||||
}
|
||||
|
||||
graphql_mutation(:destroy_snippet, variables)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:destroy_snippet)
|
||||
end
|
||||
|
||||
shared_examples 'graphql delete actions' do
|
||||
context 'when the user does not have permission' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'a mutation that returns top-level errors',
|
||||
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
|
||||
|
||||
it 'does not destroy the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.not_to change { Snippet.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has permission' do
|
||||
it 'destroys the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change { Snippet.count }.by(-1)
|
||||
end
|
||||
|
||||
it 'returns an empty Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response).to have_key('snippet')
|
||||
expect(mutation_response['snippet']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PersonalSnippet' do
|
||||
it_behaves_like 'graphql delete actions' do
|
||||
let_it_be(:snippet) { create(:personal_snippet) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ProjectSnippet' do
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:snippet) { create(:project_snippet, :private, project: project, author: create(:user)) }
|
||||
|
||||
context 'when the author is not a member of the project' do
|
||||
it 'returns an an error' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the author is a member of the project' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'graphql delete actions'
|
||||
|
||||
context 'when the snippet project feature is disabled' do
|
||||
it 'returns an an error' do
|
||||
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Updating a Snippet' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:original_content) { 'Initial content' }
|
||||
let_it_be(:original_description) { 'Initial description' }
|
||||
let_it_be(:original_title) { 'Initial title' }
|
||||
let_it_be(:original_file_name) { 'Initial file_name' }
|
||||
let(:updated_content) { 'Updated content' }
|
||||
let(:updated_description) { 'Updated description' }
|
||||
let(:updated_title) { 'Updated_title' }
|
||||
let(:updated_file_name) { 'Updated file_name' }
|
||||
let(:current_user) { snippet.author }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
id: GitlabSchema.id_from_object(snippet).to_s,
|
||||
content: updated_content,
|
||||
description: updated_description,
|
||||
visibility_level: 'public',
|
||||
file_name: updated_file_name,
|
||||
title: updated_title
|
||||
}
|
||||
|
||||
graphql_mutation(:update_snippet, variables)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:update_snippet)
|
||||
end
|
||||
|
||||
shared_examples 'graphql update actions' do
|
||||
context 'when the user does not have permission' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'a mutation that returns top-level errors',
|
||||
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
|
||||
|
||||
it 'does not update the Snippet' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.not_to change { snippet.reload }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has permission' do
|
||||
it 'updates the Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(snippet.reload.title).to eq(updated_title)
|
||||
end
|
||||
|
||||
it 'returns the updated Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['snippet']['content']).to eq(updated_content)
|
||||
expect(mutation_response['snippet']['title']).to eq(updated_title)
|
||||
expect(mutation_response['snippet']['description']).to eq(updated_description)
|
||||
expect(mutation_response['snippet']['fileName']).to eq(updated_file_name)
|
||||
expect(mutation_response['snippet']['visibilityLevel']).to eq('public')
|
||||
end
|
||||
|
||||
context 'when there are ActiveRecord validation errors' do
|
||||
let(:updated_title) { '' }
|
||||
|
||||
it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"]
|
||||
|
||||
it 'does not update the Snippet' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(snippet.reload.title).to eq(original_title)
|
||||
end
|
||||
|
||||
it 'returns the Snippet with its original values' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['snippet']['content']).to eq(original_content)
|
||||
expect(mutation_response['snippet']['title']).to eq(original_title)
|
||||
expect(mutation_response['snippet']['description']).to eq(original_description)
|
||||
expect(mutation_response['snippet']['fileName']).to eq(original_file_name)
|
||||
expect(mutation_response['snippet']['visibilityLevel']).to eq('private')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PersonalSnippet' do
|
||||
it_behaves_like 'graphql update actions' do
|
||||
let_it_be(:snippet) do
|
||||
create(:personal_snippet,
|
||||
:private,
|
||||
file_name: original_file_name,
|
||||
title: original_title,
|
||||
content: original_content,
|
||||
description: original_description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ProjectSnippet' do
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:snippet) do
|
||||
create(:project_snippet,
|
||||
:private,
|
||||
project: project,
|
||||
author: create(:user),
|
||||
file_name: original_file_name,
|
||||
title: original_title,
|
||||
content: original_content,
|
||||
description: original_description)
|
||||
end
|
||||
|
||||
context 'when the author is not a member of the project' do
|
||||
it 'returns an an error' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the author is a member of the project' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'graphql update actions'
|
||||
|
||||
context 'when the snippet project feature is disabled' do
|
||||
it 'returns an an error' do
|
||||
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue