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
0
Merge Requests
0
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
Léo-Paul Géneau
gitlab-ce
Commits
d80149e6
Commit
d80149e6
authored
Aug 17, 2018
by
Phil Hughes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added store for file templates in the Web IDE
#47947
parent
bba75044
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
584 additions
and
0 deletions
+584
-0
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+7
-0
app/assets/javascripts/ide/stores/modules/file_templates/actions.js
.../javascripts/ide/stores/modules/file_templates/actions.js
+81
-0
app/assets/javascripts/ide/stores/modules/file_templates/getters.js
.../javascripts/ide/stores/modules/file_templates/getters.js
+23
-0
app/assets/javascripts/ide/stores/modules/file_templates/index.js
...ts/javascripts/ide/stores/modules/file_templates/index.js
+12
-0
app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js
...ripts/ide/stores/modules/file_templates/mutation_types.js
+7
-0
app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
...avascripts/ide/stores/modules/file_templates/mutations.js
+21
-0
app/assets/javascripts/ide/stores/modules/file_templates/state.js
...ts/javascripts/ide/stores/modules/file_templates/state.js
+6
-0
spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
...scripts/ide/stores/modules/file_templates/actions_spec.js
+336
-0
spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
...scripts/ide/stores/modules/file_templates/getters_spec.js
+30
-0
spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
...ripts/ide/stores/modules/file_templates/mutations_spec.js
+61
-0
No files found.
app/assets/javascripts/api.js
View file @
d80149e6
...
@@ -15,6 +15,7 @@ const Api = {
...
@@ -15,6 +15,7 @@ const Api = {
mergeRequestChangesPath
:
'
/api/:version/projects/:id/merge_requests/:mrid/changes
'
,
mergeRequestChangesPath
:
'
/api/:version/projects/:id/merge_requests/:mrid/changes
'
,
mergeRequestVersionsPath
:
'
/api/:version/projects/:id/merge_requests/:mrid/versions
'
,
mergeRequestVersionsPath
:
'
/api/:version/projects/:id/merge_requests/:mrid/versions
'
,
groupLabelsPath
:
'
/groups/:namespace_path/-/labels
'
,
groupLabelsPath
:
'
/groups/:namespace_path/-/labels
'
,
templatesPath
:
'
/api/:version/templates/:key
'
,
licensePath
:
'
/api/:version/templates/licenses/:key
'
,
licensePath
:
'
/api/:version/templates/licenses/:key
'
,
gitignorePath
:
'
/api/:version/templates/gitignores/:key
'
,
gitignorePath
:
'
/api/:version/templates/gitignores/:key
'
,
gitlabCiYmlPath
:
'
/api/:version/templates/gitlab_ci_ymls/:key
'
,
gitlabCiYmlPath
:
'
/api/:version/templates/gitlab_ci_ymls/:key
'
,
...
@@ -265,6 +266,12 @@ const Api = {
...
@@ -265,6 +266,12 @@ const Api = {
});
});
},
},
templates
(
key
,
params
=
{})
{
const
url
=
Api
.
buildUrl
(
this
.
templatesPath
).
replace
(
'
:key
'
,
key
);
return
axios
.
get
(
url
,
{
params
});
},
buildUrl
(
url
)
{
buildUrl
(
url
)
{
let
urlRoot
=
''
;
let
urlRoot
=
''
;
if
(
gon
.
relative_url_root
!=
null
)
{
if
(
gon
.
relative_url_root
!=
null
)
{
...
...
app/assets/javascripts/ide/stores/modules/file_templates/actions.js
0 → 100644
View file @
d80149e6
import
Api
from
'
~/api
'
;
import
{
__
}
from
'
~/locale
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
const
requestTemplateTypes
=
({
commit
})
=>
commit
(
types
.
REQUEST_TEMPLATE_TYPES
);
export
const
receiveTemplateTypesError
=
({
commit
,
dispatch
})
=>
{
commit
(
types
.
RECEIVE_TEMPLATE_TYPES_ERROR
);
dispatch
(
'
setErrorMessage
'
,
{
text
:
__
(
'
Error loading template types.
'
),
action
:
()
=>
dispatch
(
'
fetchTemplateTypes
'
).
then
(()
=>
dispatch
(
'
setErrorMessage
'
,
null
,
{
root
:
true
}),
),
actionText
:
__
(
'
Please try again
'
),
},
{
root
:
true
},
);
};
export
const
receiveTemplateTypesSuccess
=
({
commit
},
templates
)
=>
commit
(
types
.
RECEIVE_TEMPLATE_TYPES_SUCCESS
,
templates
);
export
const
fetchTemplateTypes
=
({
dispatch
,
state
})
=>
{
if
(
!
Object
.
keys
(
state
.
selectedTemplateType
).
length
)
return
Promise
.
reject
();
dispatch
(
'
requestTemplateTypes
'
);
return
Api
.
templates
(
state
.
selectedTemplateType
.
key
)
.
then
(({
data
})
=>
dispatch
(
'
receiveTemplateTypesSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
receiveTemplateTypesError
'
));
};
export
const
setSelectedTemplateType
=
({
commit
},
type
)
=>
commit
(
types
.
SET_SELECTED_TEMPLATE_TYPE
,
type
);
export
const
receiveTemplateError
=
({
dispatch
},
template
)
=>
{
dispatch
(
'
setErrorMessage
'
,
{
text
:
__
(
'
Error loading template.
'
),
action
:
payload
=>
dispatch
(
'
fetchTemplateTypes
'
,
payload
).
then
(()
=>
dispatch
(
'
setErrorMessage
'
,
null
,
{
root
:
true
}),
),
actionText
:
__
(
'
Please try again
'
),
actionPayload
:
template
,
},
{
root
:
true
},
);
};
export
const
fetchTemplate
=
({
dispatch
,
state
},
template
)
=>
{
if
(
template
.
content
)
{
return
dispatch
(
'
setFileTemplate
'
,
template
);
}
return
Api
.
templates
(
`
${
state
.
selectedTemplateType
.
key
}
/
${
template
.
key
||
template
.
name
}
`
)
.
then
(({
data
})
=>
{
dispatch
(
'
setFileTemplate
'
,
data
);
})
.
catch
(()
=>
dispatch
(
'
receiveTemplateError
'
,
template
));
};
export
const
setFileTemplate
=
({
dispatch
,
commit
,
rootGetters
},
template
)
=>
{
dispatch
(
'
changeFileContent
'
,
{
path
:
rootGetters
.
activeFile
.
path
,
content
:
template
.
content
},
{
root
:
true
},
);
commit
(
types
.
SET_UPDATE_SUCCESS
,
true
);
};
export
const
undoFileTemplate
=
({
dispatch
,
commit
,
rootGetters
})
=>
{
const
file
=
rootGetters
.
activeFile
;
dispatch
(
'
changeFileContent
'
,
{
path
:
file
.
path
,
content
:
file
.
raw
},
{
root
:
true
});
commit
(
types
.
SET_UPDATE_SUCCESS
,
false
);
};
export
default
()
=>
{};
app/assets/javascripts/ide/stores/modules/file_templates/getters.js
0 → 100644
View file @
d80149e6
export
const
templateTypes
=
()
=>
[
{
name
:
'
.gitlab-ci.yml
'
,
key
:
'
gitlab_ci_ymls
'
,
},
{
name
:
'
.gitignore
'
,
key
:
'
gitignores
'
,
},
{
name
:
'
LICENSE
'
,
key
:
'
licenses
'
,
},
{
name
:
'
Dockerfile
'
,
key
:
'
dockerfiles
'
,
},
];
export
const
showFileTemplatesBar
=
(
_
,
getters
)
=>
name
=>
getters
.
templateTypes
.
find
(
t
=>
t
.
name
===
name
);
export
default
()
=>
{};
app/assets/javascripts/ide/stores/modules/file_templates/index.js
0 → 100644
View file @
d80149e6
import
createState
from
'
./state
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
export
default
{
namespaced
:
true
,
actions
,
state
:
createState
(),
getters
,
mutations
,
};
app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js
0 → 100644
View file @
d80149e6
export
const
REQUEST_TEMPLATE_TYPES
=
'
REQUEST_TEMPLATE_TYPES
'
;
export
const
RECEIVE_TEMPLATE_TYPES_ERROR
=
'
RECEIVE_TEMPLATE_TYPES_ERROR
'
;
export
const
RECEIVE_TEMPLATE_TYPES_SUCCESS
=
'
RECEIVE_TEMPLATE_TYPES_SUCCESS
'
;
export
const
SET_SELECTED_TEMPLATE_TYPE
=
'
SET_SELECTED_TEMPLATE_TYPE
'
;
export
const
SET_UPDATE_SUCCESS
=
'
SET_UPDATE_SUCCESS
'
;
app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
0 → 100644
View file @
d80149e6
/* eslint-disable no-param-reassign */
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
REQUEST_TEMPLATE_TYPES
](
state
)
{
state
.
isLoading
=
true
;
},
[
types
.
RECEIVE_TEMPLATE_TYPES_ERROR
](
state
)
{
state
.
isLoading
=
false
;
},
[
types
.
RECEIVE_TEMPLATE_TYPES_SUCCESS
](
state
,
templates
)
{
state
.
isLoading
=
false
;
state
.
templates
=
templates
;
},
[
types
.
SET_SELECTED_TEMPLATE_TYPE
](
state
,
type
)
{
state
.
selectedTemplateType
=
type
;
},
[
types
.
SET_UPDATE_SUCCESS
](
state
,
success
)
{
state
.
updateSuccess
=
success
;
},
};
app/assets/javascripts/ide/stores/modules/file_templates/state.js
0 → 100644
View file @
d80149e6
export
default
()
=>
({
isLoading
:
false
,
templates
:
[],
selectedTemplateType
:
{},
updateSuccess
:
false
,
});
spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
0 → 100644
View file @
d80149e6
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createState
from
'
~/ide/stores/modules/file_templates/state
'
;
import
*
as
actions
from
'
~/ide/stores/modules/file_templates/actions
'
;
import
*
as
types
from
'
~/ide/stores/modules/file_templates/mutation_types
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
describe
(
'
IDE file templates actions
'
,
()
=>
{
let
state
;
let
mock
;
beforeEach
(()
=>
{
state
=
createState
();
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
describe
(
'
requestTemplateTypes
'
,
()
=>
{
it
(
'
commits REQUEST_TEMPLATE_TYPES
'
,
done
=>
{
testAction
(
actions
.
requestTemplateTypes
,
null
,
state
,
[{
type
:
types
.
REQUEST_TEMPLATE_TYPES
}],
[],
done
,
);
});
});
describe
(
'
receiveTemplateTypesError
'
,
()
=>
{
it
(
'
commits RECEIVE_TEMPLATE_TYPES_ERROR and dispatches setErrorMessage
'
,
done
=>
{
testAction
(
actions
.
receiveTemplateTypesError
,
null
,
state
,
[{
type
:
types
.
RECEIVE_TEMPLATE_TYPES_ERROR
}],
[
{
type
:
'
setErrorMessage
'
,
payload
:
{
action
:
jasmine
.
any
(
Function
),
actionText
:
'
Please try again
'
,
text
:
'
Error loading template types.
'
,
},
},
],
done
,
);
});
});
describe
(
'
receiveTemplateTypesSuccess
'
,
()
=>
{
it
(
'
commits RECEIVE_TEMPLATE_TYPES_SUCCESS
'
,
done
=>
{
testAction
(
actions
.
receiveTemplateTypesSuccess
,
'
test
'
,
state
,
[{
type
:
types
.
RECEIVE_TEMPLATE_TYPES_SUCCESS
,
payload
:
'
test
'
}],
[],
done
,
);
});
});
describe
(
'
fetchTemplateTypes
'
,
()
=>
{
describe
(
'
success
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
/api
\/(
.*
)\/
templates
\/
licenses/
).
replyOnce
(
200
,
[
{
name
:
'
MIT
'
,
},
]);
});
it
(
'
rejects if selectedTemplateType is empty
'
,
done
=>
{
const
dispatch
=
jasmine
.
createSpy
(
'
dispatch
'
);
actions
.
fetchTemplateTypes
({
dispatch
,
state
})
.
then
(
done
.
fail
)
.
catch
(()
=>
{
expect
(
dispatch
).
not
.
toHaveBeenCalled
();
done
();
});
});
it
(
'
dispatches actions
'
,
done
=>
{
state
.
selectedTemplateType
=
{
key
:
'
licenses
'
,
};
testAction
(
actions
.
fetchTemplateTypes
,
null
,
state
,
[],
[
{
type
:
'
requestTemplateTypes
'
,
},
{
type
:
'
receiveTemplateTypesSuccess
'
,
payload
:
[
{
name
:
'
MIT
'
,
},
],
},
],
done
,
);
});
});
describe
(
'
error
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
/api
\/(
.*
)\/
templates
\/
licenses/
).
replyOnce
(
500
);
});
it
(
'
dispatches actions
'
,
done
=>
{
state
.
selectedTemplateType
=
{
key
:
'
licenses
'
,
};
testAction
(
actions
.
fetchTemplateTypes
,
null
,
state
,
[],
[
{
type
:
'
requestTemplateTypes
'
,
},
{
type
:
'
receiveTemplateTypesError
'
,
},
],
done
,
);
});
});
});
describe
(
'
setSelectedTemplateType
'
,
()
=>
{
it
(
'
commits SET_SELECTED_TEMPLATE_TYPE
'
,
done
=>
{
testAction
(
actions
.
setSelectedTemplateType
,
'
test
'
,
state
,
[{
type
:
types
.
SET_SELECTED_TEMPLATE_TYPE
,
payload
:
'
test
'
}],
[],
done
,
);
});
});
describe
(
'
receiveTemplateError
'
,
()
=>
{
it
(
'
dispatches setErrorMessage
'
,
done
=>
{
testAction
(
actions
.
receiveTemplateError
,
'
test
'
,
state
,
[],
[
{
type
:
'
setErrorMessage
'
,
payload
:
{
action
:
jasmine
.
any
(
Function
),
actionText
:
'
Please try again
'
,
text
:
'
Error loading template.
'
,
actionPayload
:
'
test
'
,
},
},
],
done
,
);
});
});
describe
(
'
fetchTemplate
'
,
()
=>
{
describe
(
'
success
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
/api
\/(
.*
)\/
templates
\/
licenses
\/
mit/
).
replyOnce
(
200
,
{
content
:
'
MIT content
'
,
});
mock
.
onGet
(
/api
\/(
.*
)\/
templates
\/
licenses
\/
testing/
).
replyOnce
(
200
,
{
content
:
'
testing content
'
,
});
});
it
(
'
dispatches setFileTemplate if template already has content
'
,
done
=>
{
const
template
=
{
content
:
'
already has content
'
,
};
testAction
(
actions
.
fetchTemplate
,
template
,
state
,
[],
[{
type
:
'
setFileTemplate
'
,
payload
:
template
}],
done
,
);
});
it
(
'
dispatches success
'
,
done
=>
{
const
template
=
{
key
:
'
mit
'
,
};
state
.
selectedTemplateType
=
{
key
:
'
licenses
'
,
};
testAction
(
actions
.
fetchTemplate
,
template
,
state
,
[],
[{
type
:
'
setFileTemplate
'
,
payload
:
{
content
:
'
MIT content
'
}
}],
done
,
);
});
it
(
'
dispatches success and uses name key for API call
'
,
done
=>
{
const
template
=
{
name
:
'
testing
'
,
};
state
.
selectedTemplateType
=
{
key
:
'
licenses
'
,
};
testAction
(
actions
.
fetchTemplate
,
template
,
state
,
[],
[{
type
:
'
setFileTemplate
'
,
payload
:
{
content
:
'
testing content
'
}
}],
done
,
);
});
});
describe
(
'
error
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
/api
\/(
.*
)\/
templates
\/
licenses
\/
mit/
).
replyOnce
(
500
);
});
it
(
'
dispatches error
'
,
done
=>
{
const
template
=
{
name
:
'
testing
'
,
};
state
.
selectedTemplateType
=
{
key
:
'
licenses
'
,
};
testAction
(
actions
.
fetchTemplate
,
template
,
state
,
[],
[{
type
:
'
receiveTemplateError
'
,
payload
:
template
}],
done
,
);
});
});
});
describe
(
'
setFileTemplate
'
,
()
=>
{
it
(
'
dispatches changeFileContent
'
,
()
=>
{
const
dispatch
=
jasmine
.
createSpy
(
'
dispatch
'
);
const
commit
=
jasmine
.
createSpy
(
'
commit
'
);
const
rootGetters
=
{
activeFile
:
{
path
:
'
test
'
},
};
actions
.
setFileTemplate
({
dispatch
,
commit
,
rootGetters
},
{
content
:
'
content
'
});
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
changeFileContent
'
,
{
path
:
'
test
'
,
content
:
'
content
'
},
{
root
:
true
},
);
});
it
(
'
commits SET_UPDATE_SUCCESS
'
,
()
=>
{
const
dispatch
=
jasmine
.
createSpy
(
'
dispatch
'
);
const
commit
=
jasmine
.
createSpy
(
'
commit
'
);
const
rootGetters
=
{
activeFile
:
{
path
:
'
test
'
},
};
actions
.
setFileTemplate
({
dispatch
,
commit
,
rootGetters
},
{
content
:
'
content
'
});
expect
(
commit
).
toHaveBeenCalledWith
(
'
SET_UPDATE_SUCCESS
'
,
true
);
});
});
describe
(
'
undoFileTemplate
'
,
()
=>
{
it
(
'
dispatches changeFileContent
'
,
()
=>
{
const
dispatch
=
jasmine
.
createSpy
(
'
dispatch
'
);
const
commit
=
jasmine
.
createSpy
(
'
commit
'
);
const
rootGetters
=
{
activeFile
:
{
path
:
'
test
'
,
raw
:
'
raw content
'
},
};
actions
.
undoFileTemplate
({
dispatch
,
commit
,
rootGetters
});
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
changeFileContent
'
,
{
path
:
'
test
'
,
content
:
'
raw content
'
},
{
root
:
true
},
);
});
it
(
'
commits SET_UPDATE_SUCCESS
'
,
()
=>
{
const
dispatch
=
jasmine
.
createSpy
(
'
dispatch
'
);
const
commit
=
jasmine
.
createSpy
(
'
commit
'
);
const
rootGetters
=
{
activeFile
:
{
path
:
'
test
'
,
raw
:
'
raw content
'
},
};
actions
.
undoFileTemplate
({
dispatch
,
commit
,
rootGetters
});
expect
(
commit
).
toHaveBeenCalledWith
(
'
SET_UPDATE_SUCCESS
'
,
false
);
});
});
});
spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
0 → 100644
View file @
d80149e6
import
*
as
getters
from
'
~/ide/stores/modules/file_templates/getters
'
;
describe
(
'
IDE file templates getters
'
,
()
=>
{
describe
(
'
templateTypes
'
,
()
=>
{
it
(
'
returns list of template types
'
,
()
=>
{
expect
(
getters
.
templateTypes
().
length
).
toBe
(
4
);
});
});
describe
(
'
showFileTemplatesBar
'
,
()
=>
{
it
(
'
finds template type by name
'
,
()
=>
{
expect
(
getters
.
showFileTemplatesBar
(
null
,
{
templateTypes
:
getters
.
templateTypes
(),
})(
'
LICENSE
'
),
).
toEqual
({
name
:
'
LICENSE
'
,
key
:
'
licenses
'
,
});
});
it
(
'
returns undefined if not found
'
,
()
=>
{
expect
(
getters
.
showFileTemplatesBar
(
null
,
{
templateTypes
:
getters
.
templateTypes
(),
})(
'
test
'
),
).
toBe
(
undefined
);
});
});
});
spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
0 → 100644
View file @
d80149e6
import
createState
from
'
~/ide/stores/modules/file_templates/state
'
;
import
*
as
types
from
'
~/ide/stores/modules/file_templates/mutation_types
'
;
import
mutations
from
'
~/ide/stores/modules/file_templates/mutations
'
;
describe
(
'
IDE file templates mutations
'
,
()
=>
{
let
state
;
beforeEach
(()
=>
{
state
=
createState
();
});
describe
(
types
.
REQUEST_TEMPLATE_TYPES
,
()
=>
{
it
(
'
sets isLoading
'
,
()
=>
{
mutations
[
types
.
REQUEST_TEMPLATE_TYPES
](
state
);
expect
(
state
.
isLoading
).
toBe
(
true
);
});
});
describe
(
types
.
RECEIVE_TEMPLATE_TYPES_ERROR
,
()
=>
{
it
(
'
sets isLoading
'
,
()
=>
{
state
.
isLoading
=
true
;
mutations
[
types
.
RECEIVE_TEMPLATE_TYPES_ERROR
](
state
);
expect
(
state
.
isLoading
).
toBe
(
false
);
});
});
describe
(
types
.
RECEIVE_TEMPLATE_TYPES_SUCCESS
,
()
=>
{
it
(
'
sets isLoading to false
'
,
()
=>
{
state
.
isLoading
=
true
;
mutations
[
types
.
RECEIVE_TEMPLATE_TYPES_SUCCESS
](
state
,
[]);
expect
(
state
.
isLoading
).
toBe
(
false
);
});
it
(
'
sets templates
'
,
()
=>
{
mutations
[
types
.
RECEIVE_TEMPLATE_TYPES_SUCCESS
](
state
,
[
'
test
'
]);
expect
(
state
.
templates
).
toEqual
([
'
test
'
]);
});
});
describe
(
types
.
SET_SELECTED_TEMPLATE_TYPE
,
()
=>
{
it
(
'
sets selectedTemplateType
'
,
()
=>
{
mutations
[
types
.
SET_SELECTED_TEMPLATE_TYPE
](
state
,
'
type
'
);
expect
(
state
.
selectedTemplateType
).
toBe
(
'
type
'
);
});
});
describe
(
types
.
SET_UPDATE_SUCCESS
,
()
=>
{
it
(
'
sets updateSuccess
'
,
()
=>
{
mutations
[
types
.
SET_UPDATE_SUCCESS
](
state
,
true
);
expect
(
state
.
updateSuccess
).
toBe
(
true
);
});
});
});
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