Commit f2a223c8 authored by Jan Provaznik's avatar Jan Provaznik Committed by Robert Speicher

Added description column to requirements

* adds both description and description_html columns
parent db258ca1
# frozen_string_literal: true
class AddDescriptionToRequirements < ActiveRecord::Migration[6.0]
DOWNTIME = false
# rubocop:disable Migration/AddLimitToTextColumns
# limit for description is added in 20200923071644_add_text_limit_to_requirements_description
# for description_html limit is not set because it's for caching purposes and
# its value is generated from `description`
def change
add_column :requirements, :description, :text
add_column :requirements, :description_html, :text
end
# rubocop:enable Migration/AddLimitToTextColumns
end
# frozen_string_literal: true
class AddTextLimitToRequirementsDescription < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_text_limit :requirements, :description, 10_000
end
def down
remove_text_limit :requirements, :description
end
end
1751fa6522a88582cb6a580acc95665f4e3f3a879f2365d5fd0a824ad1b4806d
\ No newline at end of file
0df2b1e65ef0dc563c55e575968e4fd768cec2e713e3b1c999cf584ef62b629d
\ No newline at end of file
...@@ -15365,7 +15365,10 @@ CREATE TABLE requirements ( ...@@ -15365,7 +15365,10 @@ CREATE TABLE requirements (
cached_markdown_version integer, cached_markdown_version integer,
state smallint DEFAULT 1 NOT NULL, state smallint DEFAULT 1 NOT NULL,
title character varying(255) NOT NULL, title character varying(255) NOT NULL,
title_html text title_html text,
description text,
description_html text,
CONSTRAINT check_785ae25b9d CHECK ((char_length(description) <= 10000))
); );
CREATE SEQUENCE requirements_id_seq CREATE SEQUENCE requirements_id_seq
......
...@@ -3406,14 +3406,19 @@ input CreateRequirementInput { ...@@ -3406,14 +3406,19 @@ input CreateRequirementInput {
clientMutationId: String clientMutationId: String
""" """
The project full path the requirement is associated with Description of the requirement
"""
description: String
"""
Full project path the requirement is associated with
""" """
projectPath: ID! projectPath: ID!
""" """
Title of the requirement Title of the requirement
""" """
title: String! title: String
} }
""" """
...@@ -3431,7 +3436,7 @@ type CreateRequirementPayload { ...@@ -3431,7 +3436,7 @@ type CreateRequirementPayload {
errors: [String!]! errors: [String!]!
""" """
The requirement after mutation Requirement after mutation
""" """
requirement: Requirement requirement: Requirement
} }
...@@ -15681,6 +15686,16 @@ type Requirement { ...@@ -15681,6 +15686,16 @@ type Requirement {
""" """
createdAt: Time! createdAt: Time!
"""
Description of the requirement
"""
description: String
"""
The GitLab Flavored Markdown rendering of `description`
"""
descriptionHtml: String
""" """
ID of the requirement ID of the requirement
""" """
...@@ -15741,6 +15756,11 @@ type Requirement { ...@@ -15741,6 +15756,11 @@ type Requirement {
""" """
title: String title: String
"""
The GitLab Flavored Markdown rendering of `title`
"""
titleHtml: String
""" """
Timestamp of when the requirement was last updated Timestamp of when the requirement was last updated
""" """
...@@ -19004,6 +19024,11 @@ input UpdateRequirementInput { ...@@ -19004,6 +19024,11 @@ input UpdateRequirementInput {
""" """
clientMutationId: String clientMutationId: String
"""
Description of the requirement
"""
description: String
""" """
The iid of the requirement to update The iid of the requirement to update
""" """
...@@ -19015,7 +19040,7 @@ input UpdateRequirementInput { ...@@ -19015,7 +19040,7 @@ input UpdateRequirementInput {
lastTestReportState: TestReportState lastTestReportState: TestReportState
""" """
The project full path the requirement is associated with Full project path the requirement is associated with
""" """
projectPath: ID! projectPath: ID!
...@@ -19045,7 +19070,7 @@ type UpdateRequirementPayload { ...@@ -19045,7 +19070,7 @@ type UpdateRequirementPayload {
errors: [String!]! errors: [String!]!
""" """
The requirement after mutation Requirement after mutation
""" """
requirement: Requirement requirement: Requirement
} }
......
...@@ -9153,19 +9153,25 @@ ...@@ -9153,19 +9153,25 @@
"name": "title", "name": "title",
"description": "Title of the requirement", "description": "Title of the requirement",
"type": { "type": {
"kind": "NON_NULL", "kind": "SCALAR",
"name": null, "name": "String",
"ofType": { "ofType": null
"kind": "SCALAR", },
"name": "String", "defaultValue": null
"ofType": null },
} {
"name": "description",
"description": "Description of the requirement",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}, },
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "projectPath", "name": "projectPath",
"description": "The project full path the requirement is associated with", "description": "Full project path the requirement is associated with",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
...@@ -9239,7 +9245,7 @@ ...@@ -9239,7 +9245,7 @@
}, },
{ {
"name": "requirement", "name": "requirement",
"description": "The requirement after mutation", "description": "Requirement after mutation",
"args": [ "args": [
], ],
...@@ -45414,6 +45420,34 @@ ...@@ -45414,6 +45420,34 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "description",
"description": "Description of the requirement",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "descriptionHtml",
"description": "The GitLab Flavored Markdown rendering of `description`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "id", "name": "id",
"description": "ID of the requirement", "description": "ID of the requirement",
...@@ -45577,6 +45611,20 @@ ...@@ -45577,6 +45611,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "titleHtml",
"description": "The GitLab Flavored Markdown rendering of `title`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "updatedAt", "name": "updatedAt",
"description": "Timestamp of when the requirement was last updated", "description": "Timestamp of when the requirement was last updated",
...@@ -55352,38 +55400,48 @@ ...@@ -55352,38 +55400,48 @@
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "state", "name": "description",
"description": "State of the requirement", "description": "Description of the requirement",
"type": { "type": {
"kind": "ENUM", "kind": "SCALAR",
"name": "RequirementState", "name": "String",
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "iid", "name": "projectPath",
"description": "The iid of the requirement to update", "description": "Full project path the requirement is associated with",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "String", "name": "ID",
"ofType": null "ofType": null
} }
}, },
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "projectPath", "name": "state",
"description": "The project full path the requirement is associated with", "description": "State of the requirement",
"type": {
"kind": "ENUM",
"name": "RequirementState",
"ofType": null
},
"defaultValue": null
},
{
"name": "iid",
"description": "The iid of the requirement to update",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "ID", "name": "String",
"ofType": null "ofType": null
} }
}, },
...@@ -55461,7 +55519,7 @@ ...@@ -55461,7 +55519,7 @@
}, },
{ {
"name": "requirement", "name": "requirement",
"description": "The requirement after mutation", "description": "Requirement after mutation",
"args": [ "args": [
], ],
...@@ -550,7 +550,7 @@ Autogenerated return type of CreateRequirement. ...@@ -550,7 +550,7 @@ Autogenerated return type of CreateRequirement.
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `requirement` | Requirement | The requirement after mutation | | `requirement` | Requirement | Requirement after mutation |
### CreateSnippetPayload ### CreateSnippetPayload
...@@ -2126,12 +2126,15 @@ Represents a requirement. ...@@ -2126,12 +2126,15 @@ Represents a requirement.
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `author` | User! | Author of the requirement | | `author` | User! | Author of the requirement |
| `createdAt` | Time! | Timestamp of when the requirement was created | | `createdAt` | Time! | Timestamp of when the requirement was created |
| `description` | String | Description of the requirement |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `id` | ID! | ID of the requirement | | `id` | ID! | ID of the requirement |
| `iid` | ID! | Internal ID of the requirement | | `iid` | ID! | Internal ID of the requirement |
| `lastTestReportState` | TestReportState | Latest requirement test report state | | `lastTestReportState` | TestReportState | Latest requirement test report state |
| `project` | Project! | Project to which the requirement belongs | | `project` | Project! | Project to which the requirement belongs |
| `state` | RequirementState! | State of the requirement | | `state` | RequirementState! | State of the requirement |
| `title` | String | Title of the requirement | | `title` | String | Title of the requirement |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `updatedAt` | Time! | Timestamp of when the requirement was last updated | | `updatedAt` | Time! | Timestamp of when the requirement was last updated |
| `userPermissions` | RequirementPermissions! | Permissions for the current user on the resource | | `userPermissions` | RequirementPermissions! | Permissions for the current user on the resource |
...@@ -2712,7 +2715,7 @@ Autogenerated return type of UpdateRequirement. ...@@ -2712,7 +2715,7 @@ Autogenerated return type of UpdateRequirement.
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `requirement` | Requirement | The requirement after mutation | | `requirement` | Requirement | Requirement after mutation |
### UpdateSnippetPayload ### UpdateSnippetPayload
......
# frozen_string_literal: true
module Mutations
module RequirementsManagement
class BaseRequirement < BaseMutation
include ResolvesProject
field :requirement, Types::RequirementsManagement::RequirementType,
null: true,
description: 'Requirement after mutation'
argument :title, GraphQL::STRING_TYPE,
required: false,
description: 'Title of the requirement'
argument :description, GraphQL::STRING_TYPE,
required: false,
description: 'Description of the requirement'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'Full project path the requirement is associated with'
end
end
end
...@@ -2,25 +2,11 @@ ...@@ -2,25 +2,11 @@
module Mutations module Mutations
module RequirementsManagement module RequirementsManagement
class CreateRequirement < BaseMutation class CreateRequirement < BaseRequirement
include ResolvesProject
graphql_name 'CreateRequirement' graphql_name 'CreateRequirement'
authorize :create_requirement authorize :create_requirement
field :requirement, Types::RequirementsManagement::RequirementType,
null: true,
description: 'The requirement after mutation'
argument :title, GraphQL::STRING_TYPE,
required: true,
description: 'Title of the requirement'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project full path the requirement is associated with'
def resolve(args) def resolve(args)
project_path = args.delete(:project_path) project_path = args.delete(:project_path)
project = authorized_find!(full_path: project_path) project = authorized_find!(full_path: project_path)
......
...@@ -2,21 +2,11 @@ ...@@ -2,21 +2,11 @@
module Mutations module Mutations
module RequirementsManagement module RequirementsManagement
class UpdateRequirement < BaseMutation class UpdateRequirement < BaseRequirement
include ResolvesProject
graphql_name 'UpdateRequirement' graphql_name 'UpdateRequirement'
authorize :update_requirement authorize :update_requirement
field :requirement, Types::RequirementsManagement::RequirementType,
null: true,
description: 'The requirement after mutation'
argument :title, GraphQL::STRING_TYPE,
required: false,
description: 'Title of the requirement'
argument :state, Types::RequirementsManagement::RequirementStateEnum, argument :state, Types::RequirementsManagement::RequirementStateEnum,
required: false, required: false,
description: 'State of the requirement' description: 'State of the requirement'
...@@ -25,18 +15,15 @@ module Mutations ...@@ -25,18 +15,15 @@ module Mutations
required: true, required: true,
description: 'The iid of the requirement to update' description: 'The iid of the requirement to update'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project full path the requirement is associated with'
argument :last_test_report_state, Types::RequirementsManagement::TestReportStateEnum, argument :last_test_report_state, Types::RequirementsManagement::TestReportStateEnum,
required: false, required: false,
description: 'Creates a test report for the requirement with the given state' description: 'Creates a test report for the requirement with the given state'
def ready?(**args) def ready?(**args)
if args.values_at(:title, :state, :last_test_report_state).compact.blank? update_args = [:title, :state, :last_test_report_state, :description]
if args.values_at(*update_args).compact.blank?
raise Gitlab::Graphql::Errors::ArgumentError, raise Gitlab::Graphql::Errors::ArgumentError,
'title, state or last_test_report_state argument is required' "At least one of #{update_args.join(', ')} is required"
end end
super super
......
...@@ -12,17 +12,28 @@ module Types ...@@ -12,17 +12,28 @@ module Types
field :id, GraphQL::ID_TYPE, null: false, field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the requirement' description: 'ID of the requirement'
field :iid, GraphQL::ID_TYPE, null: false, field :iid, GraphQL::ID_TYPE, null: false,
description: 'Internal ID of the requirement' description: 'Internal ID of the requirement'
field :title, GraphQL::STRING_TYPE, null: true, field :title, GraphQL::STRING_TYPE, null: true,
description: 'Title of the requirement' description: 'Title of the requirement'
markdown_field :title_html, null: true
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the requirement'
markdown_field :description_html, null: true
field :state, RequirementsManagement::RequirementStateEnum, null: false, field :state, RequirementsManagement::RequirementStateEnum, null: false,
description: 'State of the requirement' description: 'State of the requirement'
field :last_test_report_state, RequirementsManagement::TestReportStateEnum, null: true, complexity: 5, field :last_test_report_state, RequirementsManagement::TestReportStateEnum, null: true, complexity: 5,
description: 'Latest requirement test report state' description: 'Latest requirement test report state'
field :project, ProjectType, null: false, field :project, ProjectType, null: false,
description: 'Project to which the requirement belongs', description: 'Project to which the requirement belongs',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.project_id).find } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.project_id).find }
field :author, UserType, null: false, field :author, UserType, null: false,
description: 'Author of the requirement', description: 'Author of the requirement',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
...@@ -33,6 +44,7 @@ module Types ...@@ -33,6 +44,7 @@ module Types
field :created_at, Types::TimeType, null: false, field :created_at, Types::TimeType, null: false,
description: 'Timestamp of when the requirement was created' description: 'Timestamp of when the requirement was created'
field :updated_at, Types::TimeType, null: false, field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of when the requirement was last updated' description: 'Timestamp of when the requirement was last updated'
end end
......
...@@ -14,6 +14,7 @@ module RequirementsManagement ...@@ -14,6 +14,7 @@ module RequirementsManagement
self.table_name = 'requirements' self.table_name = 'requirements'
cache_markdown_field :title, pipeline: :single_line cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description, issuable_state_filter_enabled: true
strip_attributes :title strip_attributes :title
......
...@@ -14,7 +14,7 @@ module RequirementsManagement ...@@ -14,7 +14,7 @@ module RequirementsManagement
private private
def whitelisted_requirement_params def whitelisted_requirement_params
params.slice(:title) params.slice(:title, :description)
end end
end end
end end
...@@ -26,7 +26,7 @@ module RequirementsManagement ...@@ -26,7 +26,7 @@ module RequirementsManagement
end end
def whitelisted_requirement_params def whitelisted_requirement_params
params.slice(:title, :state) params.slice(:title, :description, :state)
end end
end end
end end
---
title: Add description field to requirements model and expose it in GraphQL API
merge_request: 43099
author:
type: added
...@@ -18,7 +18,8 @@ RSpec.describe Mutations::RequirementsManagement::CreateRequirement do ...@@ -18,7 +18,8 @@ RSpec.describe Mutations::RequirementsManagement::CreateRequirement do
subject do subject do
mutation.resolve( mutation.resolve(
project_path: project.full_path, project_path: project.full_path,
title: 'foo' title: 'foo',
description: 'some desc'
) )
end end
...@@ -36,6 +37,7 @@ RSpec.describe Mutations::RequirementsManagement::CreateRequirement do ...@@ -36,6 +37,7 @@ RSpec.describe Mutations::RequirementsManagement::CreateRequirement do
it 'creates new requirement' do it 'creates new requirement' do
expect(subject[:requirement][:title]).to eq('foo') expect(subject[:requirement][:title]).to eq('foo')
expect(subject[:requirement][:description]).to eq('some desc')
expect(subject[:errors]).to be_empty expect(subject[:errors]).to be_empty
end end
end end
......
...@@ -21,6 +21,7 @@ RSpec.describe Mutations::RequirementsManagement::UpdateRequirement do ...@@ -21,6 +21,7 @@ RSpec.describe Mutations::RequirementsManagement::UpdateRequirement do
project_path: project.full_path, project_path: project.full_path,
iid: requirement.iid.to_s, iid: requirement.iid.to_s,
title: 'foo', title: 'foo',
description: 'some desc',
state: 'archived', state: 'archived',
last_test_report_state: 'passed' last_test_report_state: 'passed'
) )
...@@ -41,6 +42,7 @@ RSpec.describe Mutations::RequirementsManagement::UpdateRequirement do ...@@ -41,6 +42,7 @@ RSpec.describe Mutations::RequirementsManagement::UpdateRequirement do
it 'updates new requirement', :aggregate_failures do it 'updates new requirement', :aggregate_failures do
expect(subject[:requirement]).to have_attributes( expect(subject[:requirement]).to have_attributes(
title: 'foo', title: 'foo',
description: 'some desc',
state: 'archived', state: 'archived',
last_test_report_state: 'passed' last_test_report_state: 'passed'
) )
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['Requirement'] do RSpec.describe GitlabSchema.types['Requirement'] do
fields = %i[id iid title state last_test_report_state project author created_at updated_at user_permissions test_reports] fields = %i[id iid title titleHtml description descriptionHtml state
last_test_report_state project author created_at updated_at
user_permissions test_reports]
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Requirement) } it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Requirement) }
......
...@@ -81,7 +81,7 @@ RSpec.describe 'Updating a Requirement' do ...@@ -81,7 +81,7 @@ RSpec.describe 'Updating a Requirement' do
let(:attributes) { {} } let(:attributes) { {} }
it_behaves_like 'a mutation that returns top-level errors', it_behaves_like 'a mutation that returns top-level errors',
errors: ['title, state or last_test_report_state argument is required'] errors: ['At least one of title, state, last_test_report_state, description is required']
end 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