Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
013d46b7
Commit
013d46b7
authored
Jan 16, 2019
by
Hugo Häggmark
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactored ValueMappings
parent
c90979a8
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
112 additions
and
111 deletions
+112
-111
packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
+17
-19
packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx
+12
-15
packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx
+28
-24
packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap
+6
-6
packages/grafana-ui/src/types/gauge.ts
+0
-16
packages/grafana-ui/src/types/index.ts
+0
-1
packages/grafana-ui/src/types/panel.ts
+2
-0
public/app/plugins/panel/gauge/GaugeOptionsEditor.tsx
+2
-1
public/app/plugins/panel/gauge/GaugePanel.tsx
+2
-1
public/app/plugins/panel/gauge/GaugePanelOptions.tsx
+15
-4
public/app/plugins/panel/gauge/ValueOptions.tsx
+2
-1
public/app/plugins/panel/gauge/types.ts
+15
-1
public/app/viz/Gauge.test.tsx
+1
-1
public/app/viz/Gauge.tsx
+10
-21
No files found.
packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
{
ValueMap
,
RangeMap
,
MappingType
}
from
'../../types/panel'
;
import
{
MappingType
,
ValueMapping
}
from
'../../types/panel'
;
import
{
Label
}
from
'../Label/Label'
;
import
{
Select
}
from
'../Select/Select'
;
interface
Props
{
mapping
:
ValueMap
|
RangeMap
;
update
Mapping
:
(
m
apping
)
=>
void
;
removeMapping
:
()
=>
void
;
export
interface
Props
{
valueMapping
:
ValueMapping
;
update
ValueMapping
:
(
valueMapping
:
ValueM
apping
)
=>
void
;
remove
Value
Mapping
:
()
=>
void
;
}
interface
State
{
from
:
string
;
from
?
:
string
;
id
:
number
;
operator
:
string
;
text
:
string
;
to
:
string
;
to
?
:
string
;
type
:
MappingType
;
value
:
string
;
value
?
:
string
;
}
const
mappingOptions
=
[
...
...
@@ -26,36 +26,34 @@ const mappingOptions = [
];
export
default
class
MappingRow
extends
PureComponent
<
Props
,
State
>
{
constructor
(
props
)
{
constructor
(
props
:
Props
)
{
super
(
props
);
this
.
state
=
{
...
props
.
mapping
,
};
this
.
state
=
{
...
props
.
valueMapping
};
}
onMappingValueChange
=
event
=>
{
onMappingValueChange
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
this
.
setState
({
value
:
event
.
target
.
value
});
};
onMappingFromChange
=
event
=>
{
onMappingFromChange
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
this
.
setState
({
from
:
event
.
target
.
value
});
};
onMappingToChange
=
event
=>
{
onMappingToChange
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
this
.
setState
({
to
:
event
.
target
.
value
});
};
onMappingTextChange
=
event
=>
{
onMappingTextChange
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
this
.
setState
({
text
:
event
.
target
.
value
});
};
onMappingTypeChange
=
mappingType
=>
{
onMappingTypeChange
=
(
mappingType
:
MappingType
)
=>
{
this
.
setState
({
type
:
mappingType
});
};
updateMapping
=
()
=>
{
this
.
props
.
update
Mapping
({
...
this
.
state
}
);
this
.
props
.
update
ValueMapping
({
...
this
.
state
}
as
ValueMapping
);
};
renderRow
()
{
...
...
@@ -137,7 +135,7 @@ export default class MappingRow extends PureComponent<Props, State> {
</
div
>
{
this
.
renderRow
()
}
<
div
className=
"gf-form"
>
<
button
onClick=
{
this
.
props
.
removeMapping
}
className=
"gf-form-label gf-form-label--btn"
>
<
button
onClick=
{
this
.
props
.
remove
Value
Mapping
}
className=
"gf-form-label gf-form-label--btn"
>
<
i
className=
"fa fa-times"
/>
</
button
>
</
div
>
...
...
packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx
View file @
013d46b7
import
React
from
'react'
;
import
{
shallow
}
from
'enzyme'
;
import
{
defaultProps
}
from
'app/plugins/panel/gauge/GaugePanelOptions'
;
import
{
ValueMappingsEditor
}
from
'./ValueMappingsEditor'
;
import
{
PanelOptionsProps
,
MappingType
}
from
'../../types/panel'
;
import
{
GaugeOptions
}
from
'../../types/gauge'
;
import
{
ValueMappingsEditor
,
Props
}
from
'./ValueMappingsEditor'
;
import
{
MappingType
}
from
'../../types/panel'
;
const
setup
=
(
propOverrides
?:
object
)
=>
{
const
props
:
P
anelOptionsProps
<
GaugeOptions
>
=
{
const
props
:
P
rops
=
{
onChange
:
jest
.
fn
(),
options
:
{
...
defaultProps
.
options
,
mappings
:
[
{
id
:
1
,
operator
:
''
,
type
:
MappingType
.
ValueToText
,
value
:
'20'
,
text
:
'Ok'
},
{
id
:
2
,
operator
:
''
,
type
:
MappingType
.
RangeToText
,
from
:
'21'
,
to
:
'30'
,
text
:
'Meh'
},
],
},
valueMappings
:
[
{
id
:
1
,
operator
:
''
,
type
:
MappingType
.
ValueToText
,
value
:
'20'
,
text
:
'Ok'
},
{
id
:
2
,
operator
:
''
,
type
:
MappingType
.
RangeToText
,
from
:
'21'
,
to
:
'30'
,
text
:
'Meh'
},
],
};
Object
.
assign
(
props
,
propOverrides
);
...
...
@@ -41,18 +36,20 @@ describe('Render', () => {
describe
(
'On remove mapping'
,
()
=>
{
it
(
'Should remove mapping with id 0'
,
()
=>
{
const
{
instance
}
=
setup
();
instance
.
onRemoveMapping
(
1
);
expect
(
instance
.
state
.
m
appings
).
toEqual
([
expect
(
instance
.
state
.
valueM
appings
).
toEqual
([
{
id
:
2
,
operator
:
''
,
type
:
MappingType
.
RangeToText
,
from
:
'21'
,
to
:
'30'
,
text
:
'Meh'
},
]);
});
it
(
'should remove mapping with id 1'
,
()
=>
{
const
{
instance
}
=
setup
();
instance
.
onRemoveMapping
(
2
);
expect
(
instance
.
state
.
m
appings
).
toEqual
([
expect
(
instance
.
state
.
valueM
appings
).
toEqual
([
{
id
:
1
,
operator
:
''
,
type
:
MappingType
.
ValueToText
,
value
:
'20'
,
text
:
'Ok'
},
]);
});
...
...
@@ -68,7 +65,7 @@ describe('Next id to add', () => {
});
it
(
'should default to 1'
,
()
=>
{
const
{
instance
}
=
setup
({
options
:
{
...
defaultProps
.
options
}
});
const
{
instance
}
=
setup
({
valueMappings
:
[]
});
expect
(
instance
.
state
.
nextIdToAdd
).
toEqual
(
1
);
});
...
...
packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
MappingRow
from
'./MappingRow'
;
import
{
PanelOptionsProps
,
ValueMap
,
RangeMap
,
MappingType
}
from
'../../types/panel'
;
import
{
GaugeOptions
}
from
'../../types/gauge'
;
import
{
MappingType
,
ValueMapping
}
from
'../../types/panel'
;
import
{
PanelOptionsGroup
}
from
'../PanelOptionsGroup/PanelOptionsGroup'
;
export
interface
Props
{
valueMappings
:
ValueMapping
[];
onChange
:
(
valueMappings
:
ValueMapping
[])
=>
void
;
}
interface
State
{
mappings
:
Array
<
ValueMap
|
RangeMap
>
;
valueMappings
:
ValueMapping
[]
;
nextIdToAdd
:
number
;
}
export
class
ValueMappingsEditor
extends
PureComponent
<
P
anelOptionsProps
<
GaugeOptions
>
,
State
>
{
constructor
(
props
)
{
export
class
ValueMappingsEditor
extends
PureComponent
<
P
rops
,
State
>
{
constructor
(
props
:
Props
)
{
super
(
props
);
const
mappings
=
props
.
options
.
m
appings
;
const
mappings
=
props
.
valueM
appings
;
this
.
state
=
{
mappings
:
mappings
||
[]
,
nextIdToAdd
:
mappings
.
length
>
0
?
this
.
getMaxIdFromMappings
(
mappings
)
:
1
,
valueMappings
:
mappings
,
nextIdToAdd
:
mappings
.
length
>
0
?
this
.
getMaxIdFrom
Value
Mappings
(
mappings
)
:
1
,
};
}
getMaxIdFrom
Mappings
(
mappings
)
{
getMaxIdFrom
ValueMappings
(
mappings
:
ValueMapping
[]
)
{
return
Math
.
max
.
apply
(
null
,
mappings
.
map
(
mapping
=>
mapping
.
id
).
map
(
m
=>
m
))
+
1
;
}
addMapping
=
()
=>
this
.
setState
(
prevState
=>
({
m
appings
:
[
...
prevState
.
m
appings
,
valueM
appings
:
[
...
prevState
.
valueM
appings
,
{
id
:
prevState
.
nextIdToAdd
,
operator
:
''
,
...
...
@@ -43,23 +47,23 @@ export class ValueMappingsEditor extends PureComponent<PanelOptionsProps<GaugeOp
nextIdToAdd
:
prevState
.
nextIdToAdd
+
1
,
}));
onRemoveMapping
=
id
=>
{
onRemoveMapping
=
(
id
:
number
)
=>
{
this
.
setState
(
prevState
=>
({
mappings
:
prevState
.
m
appings
.
filter
(
m
=>
{
valueMappings
:
prevState
.
valueM
appings
.
filter
(
m
=>
{
return
m
.
id
!==
id
;
}),
}),
()
=>
{
this
.
props
.
onChange
(
{
...
this
.
props
.
options
,
mappings
:
this
.
state
.
mappings
}
);
this
.
props
.
onChange
(
this
.
state
.
valueMappings
);
}
);
};
updateGauge
=
mapping
=>
{
updateGauge
=
(
mapping
:
ValueMapping
)
=>
{
this
.
setState
(
prevState
=>
({
mappings
:
prevState
.
m
appings
.
map
(
m
=>
{
valueMappings
:
prevState
.
valueM
appings
.
map
(
m
=>
{
if
(
m
.
id
===
mapping
.
id
)
{
return
{
...
mapping
};
}
...
...
@@ -68,24 +72,24 @@ export class ValueMappingsEditor extends PureComponent<PanelOptionsProps<GaugeOp
}),
}),
()
=>
{
this
.
props
.
onChange
(
{
...
this
.
props
.
options
,
mappings
:
this
.
state
.
mappings
}
);
this
.
props
.
onChange
(
this
.
state
.
valueMappings
);
}
);
};
render
()
{
const
{
m
appings
}
=
this
.
state
;
const
{
valueM
appings
}
=
this
.
state
;
return
(
<
PanelOptionsGroup
title=
"Value Mappings"
>
<
div
>
{
m
appings
.
length
>
0
&&
mappings
.
map
((
m
apping
,
index
)
=>
(
{
valueM
appings
.
length
>
0
&&
valueMappings
.
map
((
valueM
apping
,
index
)
=>
(
<
MappingRow
key=
{
`${
m
apping.text}-${index}`
}
mapping=
{
m
apping
}
updateMapping=
{
this
.
updateGauge
}
remove
Mapping=
{
()
=>
this
.
onRemoveMapping
(
m
apping
.
id
)
}
key=
{
`${
valueM
apping.text}-${index}`
}
valueMapping=
{
valueM
apping
}
update
Value
Mapping=
{
this
.
updateGauge
}
remove
ValueMapping=
{
()
=>
this
.
onRemoveMapping
(
valueM
apping
.
id
)
}
/>
))
}
</
div
>
...
...
packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap
View file @
013d46b7
...
...
@@ -7,7 +7,9 @@ exports[`Render should render component 1`] = `
<div>
<MappingRow
key="Ok-0"
mapping={
removeValueMapping={[Function]}
updateValueMapping={[Function]}
valueMapping={
Object {
"id": 1,
"operator": "",
...
...
@@ -16,12 +18,12 @@ exports[`Render should render component 1`] = `
"value": "20",
}
}
removeMapping={[Function]}
updateMapping={[Function]}
/>
<MappingRow
key="Meh-1"
mapping={
removeValueMapping={[Function]}
updateValueMapping={[Function]}
valueMapping={
Object {
"from": "21",
"id": 2,
...
...
@@ -31,8 +33,6 @@ exports[`Render should render component 1`] = `
"type": 2,
}
}
removeMapping={[Function]}
updateMapping={[Function]}
/>
</div>
<div
...
...
packages/grafana-ui/src/types/gauge.ts
deleted
100644 → 0
View file @
c90979a8
import
{
RangeMap
,
Threshold
,
ValueMap
}
from
'./panel'
;
export
interface
GaugeOptions
{
baseColor
:
string
;
decimals
:
number
;
mappings
:
Array
<
RangeMap
|
ValueMap
>
;
maxValue
:
number
;
minValue
:
number
;
prefix
:
string
;
showThresholdLabels
:
boolean
;
showThresholdMarkers
:
boolean
;
stat
:
string
;
suffix
:
string
;
thresholds
:
Threshold
[];
unit
:
string
;
}
packages/grafana-ui/src/types/index.ts
View file @
013d46b7
export
*
from
'./series'
;
export
*
from
'./time'
;
export
*
from
'./panel'
;
export
*
from
'./gauge'
;
packages/grafana-ui/src/types/panel.ts
View file @
013d46b7
...
...
@@ -56,6 +56,8 @@ interface BaseMap {
type
:
MappingType
;
}
export
type
ValueMapping
=
ValueMap
|
RangeMap
;
export
interface
ValueMap
extends
BaseMap
{
value
:
string
;
}
...
...
public/app/plugins/panel/gauge/GaugeOptionsEditor.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
{
GaugeOptions
,
PanelOptionsProps
,
PanelOptionsGroup
,
Label
}
from
'@grafana/ui'
;
import
{
PanelOptionsProps
,
PanelOptionsGroup
,
Label
}
from
'@grafana/ui'
;
import
{
Switch
}
from
'app/core/components/Switch/Switch'
;
import
{
GaugeOptions
}
from
'./types'
;
export
default
class
GaugeOptionsEditor
extends
PureComponent
<
PanelOptionsProps
<
GaugeOptions
>>
{
onToggleThresholdLabels
=
()
=>
...
...
public/app/plugins/panel/gauge/GaugePanel.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
{
GaugeOptions
,
PanelProps
,
NullValueMode
}
from
'@grafana/ui'
;
import
{
PanelProps
,
NullValueMode
}
from
'@grafana/ui'
;
import
{
getTimeSeriesVMs
}
from
'app/viz/state/timeSeries'
;
import
Gauge
from
'app/viz/Gauge'
;
import
{
GaugeOptions
}
from
'./types'
;
interface
Props
extends
PanelProps
<
GaugeOptions
>
{}
...
...
public/app/plugins/panel/gauge/GaugePanelOptions.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
{
BasicGaugeColor
,
GaugeOptions
,
PanelOptionsProps
,
ThresholdsEditor
,
Threshold
,
PanelOptionsGrid
,
ValueMappingsEditor
,
ValueMapping
,
}
from
'@grafana/ui'
;
import
ValueOptions
from
'app/plugins/panel/gauge/ValueOptions'
;
import
GaugeOptionsEditor
from
'./GaugeOptionsEditor'
;
import
{
GaugeOptions
}
from
'./types'
;
export
const
defaultProps
=
{
options
:
{
...
...
@@ -24,7 +25,7 @@ export const defaultProps = {
decimals
:
0
,
stat
:
'avg'
,
unit
:
'none'
,
m
appings
:
[],
valueM
appings
:
[],
thresholds
:
[],
},
};
...
...
@@ -32,7 +33,17 @@ export const defaultProps = {
export
default
class
GaugePanelOptions
extends
PureComponent
<
PanelOptionsProps
<
GaugeOptions
>>
{
static
defaultProps
=
defaultProps
;
onThresholdsChanged
=
(
thresholds
:
Threshold
[])
=>
this
.
props
.
onChange
({
...
this
.
props
.
options
,
thresholds
});
onThresholdsChanged
=
(
thresholds
:
Threshold
[])
=>
this
.
props
.
onChange
({
...
this
.
props
.
options
,
thresholds
,
});
onValueMappingsChanged
=
(
valueMappings
:
ValueMapping
[])
=>
this
.
props
.
onChange
({
...
this
.
props
.
options
,
valueMappings
,
});
render
()
{
const
{
onChange
,
options
}
=
this
.
props
;
...
...
@@ -44,7 +55,7 @@ export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<G
<
ThresholdsEditor
onChange=
{
this
.
onThresholdsChanged
}
thresholds=
{
options
.
thresholds
}
/>
</
PanelOptionsGrid
>
<
ValueMappingsEditor
onChange=
{
onChange
}
options=
{
option
s
}
/>
<
ValueMappingsEditor
onChange=
{
this
.
onValueMappingsChanged
}
valueMappings=
{
options
.
valueMapping
s
}
/>
</>
);
}
...
...
public/app/plugins/panel/gauge/ValueOptions.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
{
GaugeOptions
,
PanelOptionsProps
,
PanelOptionsGroup
,
Label
,
Select
}
from
'@grafana/ui'
;
import
{
PanelOptionsProps
,
PanelOptionsGroup
,
Label
,
Select
}
from
'@grafana/ui'
;
import
UnitPicker
from
'app/core/components/Select/UnitPicker'
;
import
{
GaugeOptions
}
from
'./types'
;
const
statOptions
=
[
{
value
:
'min'
,
label
:
'Min'
},
...
...
public/app/plugins/panel/gauge/types.ts
View file @
013d46b7
import
{
Threshold
,
ValueMapping
}
from
'@grafana/ui'
;
export
interface
GaugeOptions
{
baseColor
:
string
;
decimals
:
number
;
valueMappings
:
ValueMapping
[];
maxValue
:
number
;
minValue
:
number
;
prefix
:
string
;
showThresholdLabels
:
boolean
;
showThresholdMarkers
:
boolean
;
stat
:
string
;
suffix
:
string
;
thresholds
:
Threshold
[];
unit
:
string
;
}
public/app/viz/Gauge.test.tsx
View file @
013d46b7
...
...
@@ -12,7 +12,7 @@ const setup = (propOverrides?: object) => {
const
props
:
Props
=
{
baseColor
:
BasicGaugeColor
.
Green
,
maxValue
:
100
,
m
appings
:
[],
valueM
appings
:
[],
minValue
:
0
,
prefix
:
''
,
showThresholdMarkers
:
true
,
...
...
public/app/viz/Gauge.tsx
View file @
013d46b7
import
React
,
{
PureComponent
}
from
'react'
;
import
$
from
'jquery'
;
import
{
BasicGaugeColor
,
Threshold
,
TimeSeriesVMs
,
RangeMap
,
ValueMap
,
MappingType
}
from
'@grafana/ui'
;
import
{
BasicGaugeColor
,
Threshold
,
TimeSeriesVMs
,
MappingType
,
ValueMapping
}
from
'@grafana/ui'
;
import
config
from
'../core/config'
;
import
kbn
from
'../core/utils/kbn'
;
...
...
@@ -9,7 +9,7 @@ export interface Props {
baseColor
:
string
;
decimals
:
number
;
height
:
number
;
mappings
:
Array
<
RangeMap
|
ValueMap
>
;
valueMappings
:
ValueMapping
[]
;
maxValue
:
number
;
minValue
:
number
;
prefix
:
string
;
...
...
@@ -29,7 +29,7 @@ export class Gauge extends PureComponent<Props> {
static
defaultProps
=
{
baseColor
:
BasicGaugeColor
.
Green
,
maxValue
:
100
,
m
appings
:
[],
valueM
appings
:
[],
minValue
:
0
,
prefix
:
''
,
showThresholdMarkers
:
true
,
...
...
@@ -64,20 +64,17 @@ export class Gauge extends PureComponent<Props> {
}
})[
0
];
return
{
rangeMap
,
valueMap
,
};
return
{
rangeMap
,
valueMap
};
}
formatValue
(
value
)
{
const
{
decimals
,
m
appings
,
prefix
,
suffix
,
unit
}
=
this
.
props
;
const
{
decimals
,
valueM
appings
,
prefix
,
suffix
,
unit
}
=
this
.
props
;
const
formatFunc
=
kbn
.
valueFormats
[
unit
];
const
formattedValue
=
formatFunc
(
value
,
decimals
);
if
(
m
appings
.
length
>
0
)
{
const
{
rangeMap
,
valueMap
}
=
this
.
formatWithMappings
(
m
appings
,
formattedValue
);
if
(
valueM
appings
.
length
>
0
)
{
const
{
rangeMap
,
valueMap
}
=
this
.
formatWithMappings
(
valueM
appings
,
formattedValue
);
if
(
valueMap
)
{
return
`
${
prefix
}
${
valueMap
}
${
suffix
}
`
;
...
...
@@ -148,10 +145,7 @@ export class Gauge extends PureComponent<Props> {
color
:
index
===
0
?
threshold
.
color
:
thresholds
[
index
].
color
,
};
}),
{
value
:
maxValue
,
color
:
thresholds
.
length
>
0
?
BasicGaugeColor
.
Red
:
baseColor
,
},
{
value
:
maxValue
,
color
:
thresholds
.
length
>
0
?
BasicGaugeColor
.
Red
:
baseColor
},
];
const
options
=
{
...
...
@@ -184,19 +178,14 @@ export class Gauge extends PureComponent<Props> {
formatter
:
()
=>
{
return
this
.
formatValue
(
value
);
},
font
:
{
size
:
fontSize
,
family
:
'"Helvetica Neue", Helvetica, Arial, sans-serif'
,
},
font
:
{
size
:
fontSize
,
family
:
'"Helvetica Neue", Helvetica, Arial, sans-serif'
},
},
show
:
true
,
},
},
};
const
plotSeries
=
{
data
:
[[
0
,
value
]],
};
const
plotSeries
=
{
data
:
[[
0
,
value
]]
};
try
{
$
.
plot
(
this
.
canvasElement
,
[
plotSeries
],
options
);
...
...
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