Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
a9616d3e
Commit
a9616d3e
authored
Oct 07, 2020
by
Alan (Maciej) Paruszewski
Committed by
Shinya Maeda
Oct 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add ability to sort vulnerabilities by state
This change adds ability to sort vulnerabilities by state in GraphQL.
parent
d617e6e0
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
123 additions
and
8 deletions
+123
-8
db/migrate/20200928164807_add_index_on_vulnerabilities_state_case.rb
...20200928164807_add_index_on_vulnerabilities_state_case.rb
+21
-0
db/schema_migrations/20200928164807
db/schema_migrations/20200928164807
+1
-0
db/structure.sql
db/structure.sql
+4
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+11
-1
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+15
-3
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+3
-1
ee/app/graphql/types/vulnerability_sort_enum.rb
ee/app/graphql/types/vulnerability_sort_enum.rb
+2
-0
ee/app/models/ee/vulnerability.rb
ee/app/models/ee/vulnerability.rb
+17
-1
ee/changelogs/unreleased/249245-add-sorting-vulnerabilities-by-state.yml
...nreleased/249245-add-sorting-vulnerabilities-by-state.yml
+5
-0
ee/spec/graphql/types/vulnerability_sort_enum_spec.rb
ee/spec/graphql/types/vulnerability_sort_enum_spec.rb
+1
-1
ee/spec/models/ee/vulnerability_spec.rb
ee/spec/models/ee/vulnerability_spec.rb
+24
-1
lib/gitlab/graphql/pagination/keyset/order_info.rb
lib/gitlab/graphql/pagination/keyset/order_info.rb
+7
-0
spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
+12
-0
No files found.
db/migrate/20200928164807_add_index_on_vulnerabilities_state_case.rb
0 → 100644
View file @
a9616d3e
# frozen_string_literal: true
class
AddIndexOnVulnerabilitiesStateCase
<
ActiveRecord
::
Migration
[
6.0
]
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
INDEX_NAME
=
'index_vulnerabilities_on_state_case_id'
STATE_ORDER_ARRAY_POSITION
=
'ARRAY_POSITION(ARRAY[1, 4, 3, 2]::smallint[], state)'
disable_ddl_transaction!
def
up
add_concurrent_index
:vulnerabilities
,
"
#{
STATE_ORDER_ARRAY_POSITION
}
, id DESC"
,
name:
INDEX_NAME
add_concurrent_index
:vulnerabilities
,
"
#{
STATE_ORDER_ARRAY_POSITION
}
DESC, id DESC"
,
name:
"
#{
INDEX_NAME
}
_desc"
end
def
down
remove_concurrent_index_by_name
:vulnerabilities
,
"
#{
INDEX_NAME
}
_desc"
remove_concurrent_index_by_name
:vulnerabilities
,
INDEX_NAME
end
end
db/schema_migrations/20200928164807
0 → 100644
View file @
a9616d3e
346d0e913212d6e84528d47228ba7e6d0cf4a396e7fc921f7c684acfaaeeedb8
\ No newline at end of file
db/structure.sql
View file @
a9616d3e
...
@@ -21603,6 +21603,10 @@ CREATE INDEX index_vulnerabilities_on_resolved_by_id ON vulnerabilities USING bt
...
@@ -21603,6 +21603,10 @@ CREATE INDEX index_vulnerabilities_on_resolved_by_id ON vulnerabilities USING bt
CREATE
INDEX
index_vulnerabilities_on_start_date_sourcing_milestone_id
ON
vulnerabilities
USING
btree
(
start_date_sourcing_milestone_id
);
CREATE
INDEX
index_vulnerabilities_on_start_date_sourcing_milestone_id
ON
vulnerabilities
USING
btree
(
start_date_sourcing_milestone_id
);
CREATE
INDEX
index_vulnerabilities_on_state_case_id
ON
vulnerabilities
USING
btree
(
array_position
(
ARRAY
[(
1
)::
smallint
,
(
4
)::
smallint
,
(
3
)::
smallint
,
(
2
)::
smallint
],
state
),
id
DESC
);
CREATE
INDEX
index_vulnerabilities_on_state_case_id_desc
ON
vulnerabilities
USING
btree
(
array_position
(
ARRAY
[(
1
)::
smallint
,
(
4
)::
smallint
,
(
3
)::
smallint
,
(
2
)::
smallint
],
state
)
DESC
,
id
DESC
);
CREATE
INDEX
index_vulnerabilities_on_updated_by_id
ON
vulnerabilities
USING
btree
(
updated_by_id
);
CREATE
INDEX
index_vulnerabilities_on_updated_by_id
ON
vulnerabilities
USING
btree
(
updated_by_id
);
CREATE
INDEX
index_vulnerability_exports_on_author_id
ON
vulnerability_exports
USING
btree
(
author_id
);
CREATE
INDEX
index_vulnerability_exports_on_author_id
ON
vulnerability_exports
USING
btree
(
author_id
);
...
...
doc/api/graphql/reference/gitlab_schema.graphql
View file @
a9616d3e
...
@@ -20158,7 +20158,7 @@ type Vulnerability implements Noteable {
...
@@ -20158,7 +20158,7 @@ type Vulnerability implements Noteable {
severity
:
VulnerabilitySeverity
severity
:
VulnerabilitySeverity
"""
"""
State
of
the
vulnerability
(
DETECTED
,
DISMISSED
,
RESOLVED
,
CONFIRM
ED
)
State
of
the
vulnerability
(
DETECTED
,
CONFIRMED
,
RESOLVED
,
DISMISS
ED
)
"""
"""
state
:
VulnerabilityState
state
:
VulnerabilityState
...
@@ -20820,6 +20820,16 @@ enum VulnerabilitySort {
...
@@ -20820,6 +20820,16 @@ enum VulnerabilitySort {
"""
"""
severity_desc
severity_desc
"""
State
in
ascending
order
"""
state_asc
"""
State
in
descending
order
"""
state_desc
"""
"""
Title
in
ascending
order
Title
in
ascending
order
"""
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
a9616d3e
...
@@ -58503,7 +58503,7 @@
...
@@ -58503,7 +58503,7 @@
},
},
{
{
"name": "state",
"name": "state",
"description": "State of the vulnerability (DETECTED,
DISMISSED, RESOLVED, CONFIRM
ED)",
"description": "State of the vulnerability (DETECTED,
CONFIRMED, RESOLVED, DISMISS
ED)",
"args": [
"args": [
],
],
...
@@ -60482,6 +60482,18 @@
...
@@ -60482,6 +60482,18 @@
"description": "Report Type in ascending order",
"description": "Report Type in ascending order",
"isDeprecated": false,
"isDeprecated": false,
"deprecationReason": null
"deprecationReason": null
},
{
"name": "state_desc",
"description": "State in descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "state_asc",
"description": "State in ascending order",
"isDeprecated": false,
"deprecationReason": null
}
}
],
],
"possibleTypes": null
"possibleTypes": null
...
@@ -60501,7 +60513,7 @@
...
@@ -60501,7 +60513,7 @@
"deprecationReason": null
"deprecationReason": null
},
},
{
{
"name": "
DISMISS
ED",
"name": "
CONFIRM
ED",
"description": null,
"description": null,
"isDeprecated": false,
"isDeprecated": false,
"deprecationReason": null
"deprecationReason": null
...
@@ -60513,7 +60525,7 @@
...
@@ -60513,7 +60525,7 @@
"deprecationReason": null
"deprecationReason": null
},
},
{
{
"name": "
CONFIRM
ED",
"name": "
DISMISS
ED",
"description": null,
"description": null,
"isDeprecated": false,
"isDeprecated": false,
"deprecationReason": null
"deprecationReason": null
doc/api/graphql/reference/index.md
View file @
a9616d3e
...
@@ -2821,7 +2821,7 @@ Represents a vulnerability.
...
@@ -2821,7 +2821,7 @@ Represents a vulnerability.
|
`resolvedOnDefaultBranch`
| Boolean! | Indicates whether the vulnerability is fixed on the default branch or not |
|
`resolvedOnDefaultBranch`
| Boolean! | Indicates whether the vulnerability is fixed on the default branch or not |
|
`scanner`
| VulnerabilityScanner | Scanner metadata for the vulnerability. |
|
`scanner`
| VulnerabilityScanner | Scanner metadata for the vulnerability. |
|
`severity`
| VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
|
`severity`
| VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
|
`state`
| VulnerabilityState | State of the vulnerability (DETECTED,
DISMISSED, RESOLVED, CONFIRM
ED) |
|
`state`
| VulnerabilityState | State of the vulnerability (DETECTED,
CONFIRMED, RESOLVED, DISMISS
ED) |
|
`title`
| String | Title of the vulnerability |
|
`title`
| String | Title of the vulnerability |
|
`userNotesCount`
| Int! | Number of user notes attached to the vulnerability |
|
`userNotesCount`
| Int! | Number of user notes attached to the vulnerability |
|
`userPermissions`
| VulnerabilityPermissions! | Permissions for the current user on the resource |
|
`userPermissions`
| VulnerabilityPermissions! | Permissions for the current user on the resource |
...
@@ -3767,6 +3767,8 @@ Vulnerability sort values.
...
@@ -3767,6 +3767,8 @@ Vulnerability sort values.
|
`report_type_desc`
| Report Type in descending order |
|
`report_type_desc`
| Report Type in descending order |
|
`severity_asc`
| Severity in ascending order |
|
`severity_asc`
| Severity in ascending order |
|
`severity_desc`
| Severity in descending order |
|
`severity_desc`
| Severity in descending order |
|
`state_asc`
| State in ascending order |
|
`state_desc`
| State in descending order |
|
`title_asc`
| Title in ascending order |
|
`title_asc`
| Title in ascending order |
|
`title_desc`
| Title in descending order |
|
`title_desc`
| Title in descending order |
...
...
ee/app/graphql/types/vulnerability_sort_enum.rb
View file @
a9616d3e
...
@@ -13,5 +13,7 @@ module Types
...
@@ -13,5 +13,7 @@ module Types
value
'detected_asc'
,
'Detection timestamp in ascending order'
value
'detected_asc'
,
'Detection timestamp in ascending order'
value
'report_type_desc'
,
'Report Type in descending order'
value
'report_type_desc'
,
'Report Type in descending order'
value
'report_type_asc'
,
'Report Type in ascending order'
value
'report_type_asc'
,
'Report Type in ascending order'
value
'state_desc'
,
'State in descending order'
value
'state_asc'
,
'State in ascending order'
end
end
end
end
ee/app/models/ee/vulnerability.rb
View file @
a9616d3e
...
@@ -54,7 +54,9 @@ module EE
...
@@ -54,7 +54,9 @@ module EE
has_many
:notes
,
as: :noteable
,
dependent: :delete_all
# rubocop:disable Cop/ActiveRecordDependent
has_many
:notes
,
as: :noteable
,
dependent: :delete_all
# rubocop:disable Cop/ActiveRecordDependent
has_many
:user_mentions
,
class_name:
'VulnerabilityUserMention'
has_many
:user_mentions
,
class_name:
'VulnerabilityUserMention'
enum
state:
{
detected:
1
,
dismissed:
2
,
resolved:
3
,
confirmed:
4
}
# keep the order of the values in the state enum, it is used in state_order method to properly order vulnerabilities based on state
# remember to recreate index_vulnerabilities_on_state_case_id index when you update or extend this enum
enum
state:
{
detected:
1
,
confirmed:
4
,
resolved:
3
,
dismissed:
2
}
enum
severity:
::
Vulnerabilities
::
Finding
::
SEVERITY_LEVELS
,
_prefix: :severity
enum
severity:
::
Vulnerabilities
::
Finding
::
SEVERITY_LEVELS
,
_prefix: :severity
enum
confidence:
::
Vulnerabilities
::
Finding
::
CONFIDENCE_LEVELS
,
_prefix: :confidence
enum
confidence:
::
Vulnerabilities
::
Finding
::
CONFIDENCE_LEVELS
,
_prefix: :confidence
enum
report_type:
::
Vulnerabilities
::
Finding
::
REPORT_TYPES
enum
report_type:
::
Vulnerabilities
::
Finding
::
REPORT_TYPES
...
@@ -105,6 +107,8 @@ module EE
...
@@ -105,6 +107,8 @@ module EE
scope
:order_created_at_desc
,
->
{
reorder
(
created_at: :desc
,
id: :desc
)
}
scope
:order_created_at_desc
,
->
{
reorder
(
created_at: :desc
,
id: :desc
)
}
scope
:order_report_type_asc
,
->
{
select
(
*
arel
.
projections
,
report_type_order
.
as
(
'case_order_value'
)).
reorder
(
report_type_order
.
asc
,
id: :desc
)
}
scope
:order_report_type_asc
,
->
{
select
(
*
arel
.
projections
,
report_type_order
.
as
(
'case_order_value'
)).
reorder
(
report_type_order
.
asc
,
id: :desc
)
}
scope
:order_report_type_desc
,
->
{
select
(
*
arel
.
projections
,
report_type_order
.
as
(
'case_order_value'
)).
reorder
(
report_type_order
.
desc
,
id: :desc
)
}
scope
:order_report_type_desc
,
->
{
select
(
*
arel
.
projections
,
report_type_order
.
as
(
'case_order_value'
)).
reorder
(
report_type_order
.
desc
,
id: :desc
)
}
scope
:order_state_asc
,
->
{
select
(
*
arel
.
projections
,
state_order
.
as
(
'array_position'
)).
reorder
(
state_order
.
asc
,
id: :desc
)
}
scope
:order_state_desc
,
->
{
select
(
*
arel
.
projections
,
state_order
.
as
(
'array_position'
)).
reorder
(
state_order
.
desc
,
id: :desc
)
}
scope
:order_id_desc
,
->
{
reorder
(
id: :desc
)
}
scope
:order_id_desc
,
->
{
reorder
(
id: :desc
)
}
scope
:with_limit
,
->
(
maximum
)
{
limit
(
maximum
)
}
scope
:with_limit
,
->
(
maximum
)
{
limit
(
maximum
)
}
...
@@ -208,6 +212,16 @@ module EE
...
@@ -208,6 +212,16 @@ module EE
end
end
end
end
def
state_order
Arel
::
Nodes
::
NamedFunction
.
new
(
'ARRAY_POSITION'
,
[
Arel
.
sql
(
"ARRAY
#{
states
.
values
}
::smallint[]"
),
arel_table
[
:state
]
]
)
end
def
active_states
def
active_states
ACTIVE_STATES
ACTIVE_STATES
end
end
...
@@ -230,6 +244,8 @@ module EE
...
@@ -230,6 +244,8 @@ module EE
when
'detected_asc'
then
order_created_at_asc
when
'detected_asc'
then
order_created_at_asc
when
'report_type_desc'
then
order_report_type_desc
when
'report_type_desc'
then
order_report_type_desc
when
'report_type_asc'
then
order_report_type_asc
when
'report_type_asc'
then
order_report_type_asc
when
'state_desc'
then
order_state_desc
when
'state_asc'
then
order_state_asc
else
else
order_severity_desc
order_severity_desc
end
end
...
...
ee/changelogs/unreleased/249245-add-sorting-vulnerabilities-by-state.yml
0 → 100644
View file @
a9616d3e
---
title
:
Add ability to sort vulnerabilities by state
merge_request
:
42973
author
:
type
:
added
ee/spec/graphql/types/vulnerability_sort_enum_spec.rb
View file @
a9616d3e
...
@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['VulnerabilitySort'] do
...
@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['VulnerabilitySort'] do
it
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'VulnerabilitySort'
)
}
it
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'VulnerabilitySort'
)
}
it
'exposes all the existing Vulnerability sort orders'
do
it
'exposes all the existing Vulnerability sort orders'
do
expect
(
described_class
.
values
.
keys
).
to
include
(
*
%w[severity_desc severity_asc title_desc title_asc detected_desc detected_asc report_type_desc report_type_asc]
)
expect
(
described_class
.
values
.
keys
).
to
include
(
*
%w[severity_desc severity_asc title_desc title_asc detected_desc detected_asc report_type_desc report_type_asc
state_desc state_asc
]
)
end
end
end
end
ee/spec/models/ee/vulnerability_spec.rb
View file @
a9616d3e
...
@@ -301,7 +301,7 @@ RSpec.describe Vulnerability do
...
@@ -301,7 +301,7 @@ RSpec.describe Vulnerability do
end
end
end
end
describe
'.order_report_type'
do
describe
'.order_report_type
_
'
do
let_it_be
(
:vulnerability_dast
)
{
create
(
:vulnerability
,
:dast
)
}
let_it_be
(
:vulnerability_dast
)
{
create
(
:vulnerability
,
:dast
)
}
let_it_be
(
:vulnerability_secret_detection
)
{
create
(
:vulnerability
,
:secret_detection
)
}
let_it_be
(
:vulnerability_secret_detection
)
{
create
(
:vulnerability
,
:secret_detection
)
}
let_it_be
(
:vulnerability_sast
)
{
create
(
:vulnerability
,
:sast
)
}
let_it_be
(
:vulnerability_sast
)
{
create
(
:vulnerability
,
:sast
)
}
...
@@ -324,6 +324,29 @@ RSpec.describe Vulnerability do
...
@@ -324,6 +324,29 @@ RSpec.describe Vulnerability do
end
end
end
end
describe
'.order_state_'
do
let_it_be
(
:vulnerability_confirmed
)
{
create
(
:vulnerability
,
:confirmed
)
}
let_it_be
(
:vulnerability_detected
)
{
create
(
:vulnerability
,
:detected
)
}
let_it_be
(
:vulnerability_dismissed
)
{
create
(
:vulnerability
,
:dismissed
)
}
let_it_be
(
:vulnerability_resolved
)
{
create
(
:vulnerability
,
:resolved
)
}
describe
'asc'
do
subject
{
described_class
.
order_state_asc
}
it
'returns vulnerabilities ordered by state'
do
is_expected
.
to
eq
([
vulnerability_detected
,
vulnerability_confirmed
,
vulnerability_resolved
,
vulnerability_dismissed
])
end
end
describe
'desc'
do
subject
{
described_class
.
order_state_desc
}
it
'returns vulnerabilities ordered by state'
do
is_expected
.
to
eq
([
vulnerability_dismissed
,
vulnerability_resolved
,
vulnerability_confirmed
,
vulnerability_detected
])
end
end
end
describe
'.with_resolution'
do
describe
'.with_resolution'
do
let_it_be
(
:vulnerability_with_resolution
)
{
create
(
:vulnerability
,
resolved_on_default_branch:
true
)
}
let_it_be
(
:vulnerability_with_resolution
)
{
create
(
:vulnerability
,
resolved_on_default_branch:
true
)
}
let_it_be
(
:vulnerability_without_resolution
)
{
create
(
:vulnerability
,
resolved_on_default_branch:
false
)
}
let_it_be
(
:vulnerability_without_resolution
)
{
create
(
:vulnerability
,
resolved_on_default_branch:
false
)
}
...
...
lib/gitlab/graphql/pagination/keyset/order_info.rb
View file @
a9616d3e
...
@@ -96,6 +96,8 @@ module Gitlab
...
@@ -96,6 +96,8 @@ module Gitlab
[
'similarity'
,
order_value
.
direction
,
order_value
.
expr
]
[
'similarity'
,
order_value
.
direction
,
order_value
.
expr
]
elsif
ordering_by_case?
(
order_value
)
elsif
ordering_by_case?
(
order_value
)
[
'case_order_value'
,
order_value
.
direction
,
order_value
.
expr
]
[
'case_order_value'
,
order_value
.
direction
,
order_value
.
expr
]
elsif
ordering_by_array_position?
(
order_value
)
[
'array_position'
,
order_value
.
direction
,
order_value
.
expr
]
else
else
[
order_value
.
expr
.
name
,
order_value
.
direction
,
nil
]
[
order_value
.
expr
.
name
,
order_value
.
direction
,
nil
]
end
end
...
@@ -106,6 +108,11 @@ module Gitlab
...
@@ -106,6 +108,11 @@ module Gitlab
order_value
.
expr
.
is_a?
(
Arel
::
Nodes
::
NamedFunction
)
&&
order_value
.
expr
&
.
name
&
.
downcase
==
'lower'
order_value
.
expr
.
is_a?
(
Arel
::
Nodes
::
NamedFunction
)
&&
order_value
.
expr
&
.
name
&
.
downcase
==
'lower'
end
end
# determine if ordering using ARRAY_POSITION, eg. "ORDER BY ARRAY_POSITION(Array[4,3,1,2]::smallint, state)"
def
ordering_by_array_position?
(
order_value
)
order_value
.
expr
.
is_a?
(
Arel
::
Nodes
::
NamedFunction
)
&&
order_value
.
expr
&
.
name
&
.
downcase
==
'array_position'
end
# determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore
# determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore
def
ordering_by_similarity?
(
order_value
)
def
ordering_by_similarity?
(
order_value
)
Gitlab
::
Database
::
SimilarityScore
.
order_by_similarity?
(
order_value
)
Gitlab
::
Database
::
SimilarityScore
.
order_by_similarity?
(
order_value
)
...
...
spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
View file @
a9616d3e
...
@@ -74,6 +74,18 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
...
@@ -74,6 +74,18 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
expect
(
order_list
.
first
.
sort_direction
).
to
eq
:asc
expect
(
order_list
.
first
.
sort_direction
).
to
eq
:asc
end
end
end
end
context
'when ordering by ARRAY_POSITION'
,
:aggregate_failuers
do
let
(
:array_position
)
{
Arel
::
Nodes
::
NamedFunction
.
new
(
'ARRAY_POSITION'
,
[
Arel
.
sql
(
"ARRAY[1,0]::smallint[]"
),
Project
.
arel_table
[
:auto_cancel_pending_pipelines
]])
}
let
(
:relation
)
{
Project
.
order
(
array_position
.
asc
)
}
it
'assigns the right attribute name, named function, and direction'
do
expect
(
order_list
.
count
).
to
eq
1
expect
(
order_list
.
first
.
attribute_name
).
to
eq
'array_position'
expect
(
order_list
.
first
.
named_function
).
to
be_kind_of
(
Arel
::
Nodes
::
NamedFunction
)
expect
(
order_list
.
first
.
sort_direction
).
to
eq
:asc
end
end
end
end
describe
'#validate_ordering'
do
describe
'#validate_ordering'
do
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment