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
5fd0d376
Commit
5fd0d376
authored
May 04, 2017
by
Adam Niedzielski
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
EE port of "Display slash commands outcome when previewing Markdown"
parent
5c29411e
Changes
28
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
804 additions
and
136 deletions
+804
-136
app/assets/javascripts/preview_markdown.js
app/assets/javascripts/preview_markdown.js
+40
-8
app/controllers/concerns/markdown_preview.rb
app/controllers/concerns/markdown_preview.rb
+0
-19
app/controllers/projects/wikis_controller.rb
app/controllers/projects/wikis_controller.rb
+8
-5
app/controllers/projects_controller.rb
app/controllers/projects_controller.rb
+9
-2
app/controllers/snippets_controller.rb
app/controllers/snippets_controller.rb
+8
-2
app/helpers/gitlab_routing_helper.rb
app/helpers/gitlab_routing_helper.rb
+4
-0
app/services/preview_markdown_service.rb
app/services/preview_markdown_service.rb
+45
-0
app/services/slash_commands/interpret_service.rb
app/services/slash_commands/interpret_service.rb
+174
-49
app/views/groups/milestones/new.html.haml
app/views/groups/milestones/new.html.haml
+1
-1
app/views/layouts/project.html.haml
app/views/layouts/project.html.haml
+0
-5
app/views/projects/_md_preview.html.haml
app/views/projects/_md_preview.html.haml
+5
-2
app/views/projects/milestones/_form.html.haml
app/views/projects/milestones/_form.html.haml
+1
-1
app/views/projects/notes/_edit_form.html.haml
app/views/projects/notes/_edit_form.html.haml
+1
-1
app/views/projects/notes/_form.html.haml
app/views/projects/notes/_form.html.haml
+5
-1
app/views/projects/notes/_notes_with_form.html.haml
app/views/projects/notes/_notes_with_form.html.haml
+1
-1
app/views/projects/releases/edit.html.haml
app/views/projects/releases/edit.html.haml
+1
-1
app/views/projects/tags/new.html.haml
app/views/projects/tags/new.html.haml
+1
-1
app/views/projects/wikis/_form.html.haml
app/views/projects/wikis/_form.html.haml
+1
-1
app/views/shared/issuable/_form.html.haml
app/views/shared/issuable/_form.html.haml
+1
-1
app/views/shared/issuable/form/_description.html.haml
app/views/shared/issuable/form/_description.html.haml
+10
-3
changelogs/unreleased/preview-separate-slash-commands.yml
changelogs/unreleased/preview-separate-slash-commands.yml
+4
-0
lib/gitlab/slash_commands/command_definition.rb
lib/gitlab/slash_commands/command_definition.rb
+36
-10
lib/gitlab/slash_commands/dsl.rb
lib/gitlab/slash_commands/dsl.rb
+47
-5
spec/lib/gitlab/slash_commands/command_definition_spec.rb
spec/lib/gitlab/slash_commands/command_definition_spec.rb
+52
-0
spec/lib/gitlab/slash_commands/dsl_spec.rb
spec/lib/gitlab/slash_commands/dsl_spec.rb
+49
-17
spec/services/preview_markdown_service_spec.rb
spec/services/preview_markdown_service_spec.rb
+67
-0
spec/services/slash_commands/interpret_service_spec.rb
spec/services/slash_commands/interpret_service_spec.rb
+218
-0
spec/support/features/issuable_slash_commands_shared_examples.rb
...pport/features/issuable_slash_commands_shared_examples.rb
+15
-0
No files found.
app/assets/javascripts/preview_markdown.js
View file @
5fd0d376
...
...
@@ -2,8 +2,9 @@
// MarkdownPreview
//
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
// and showing a warning when more than `x` users are referenced.
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview
// (including the explanation of slash commands), and showing a warning when
// more than `x` users are referenced.
//
(
function
()
{
var
lastTextareaPreviewed
;
...
...
@@ -17,32 +18,45 @@
// Minimum number of users referenced before triggering a warning
MarkdownPreview
.
prototype
.
referenceThreshold
=
10
;
MarkdownPreview
.
prototype
.
emptyMessage
=
'
Nothing to preview.
'
;
MarkdownPreview
.
prototype
.
ajaxCache
=
{};
MarkdownPreview
.
prototype
.
showPreview
=
function
(
$form
)
{
var
mdText
;
var
preview
=
$form
.
find
(
'
.js-md-preview
'
);
var
url
=
preview
.
data
(
'
url
'
);
if
(
preview
.
hasClass
(
'
md-preview-loading
'
))
{
return
;
}
mdText
=
$form
.
find
(
'
textarea.markdown-area
'
).
val
();
if
(
mdText
.
trim
().
length
===
0
)
{
preview
.
text
(
'
Nothing to preview.
'
);
preview
.
text
(
this
.
emptyMessage
);
this
.
hideReferencedUsers
(
$form
);
}
else
{
preview
.
addClass
(
'
md-preview-loading
'
).
text
(
'
Loading...
'
);
this
.
fetchMarkdownPreview
(
mdText
,
(
function
(
response
)
{
preview
.
removeClass
(
'
md-preview-loading
'
).
html
(
response
.
body
);
this
.
fetchMarkdownPreview
(
mdText
,
url
,
(
function
(
response
)
{
var
body
;
if
(
response
.
body
.
length
>
0
)
{
body
=
response
.
body
;
}
else
{
body
=
this
.
emptyMessage
;
}
preview
.
removeClass
(
'
md-preview-loading
'
).
html
(
body
);
preview
.
renderGFM
();
this
.
renderReferencedUsers
(
response
.
references
.
users
,
$form
);
if
(
response
.
references
.
commands
)
{
this
.
renderReferencedCommands
(
response
.
references
.
commands
,
$form
);
}
}).
bind
(
this
));
}
};
MarkdownPreview
.
prototype
.
fetchMarkdownPreview
=
function
(
text
,
success
)
{
if
(
!
window
.
preview_markdown_path
)
{
MarkdownPreview
.
prototype
.
fetchMarkdownPreview
=
function
(
text
,
url
,
success
)
{
if
(
!
url
)
{
return
;
}
if
(
text
===
this
.
ajaxCache
.
text
)
{
...
...
@@ -51,7 +65,7 @@
}
$
.
ajax
({
type
:
'
POST
'
,
url
:
window
.
preview_markdown_path
,
url
:
url
,
data
:
{
text
:
text
},
...
...
@@ -83,6 +97,22 @@
}
};
MarkdownPreview
.
prototype
.
hideReferencedCommands
=
function
(
$form
)
{
$form
.
find
(
'
.referenced-commands
'
).
hide
();
};
MarkdownPreview
.
prototype
.
renderReferencedCommands
=
function
(
commands
,
$form
)
{
var
referencedCommands
;
referencedCommands
=
$form
.
find
(
'
.referenced-commands
'
);
if
(
commands
.
length
>
0
)
{
referencedCommands
.
html
(
commands
);
referencedCommands
.
show
();
}
else
{
referencedCommands
.
html
(
''
);
referencedCommands
.
hide
();
}
};
return
MarkdownPreview
;
}());
...
...
@@ -137,6 +167,8 @@
$form
.
find
(
'
.md-write-holder
'
).
show
();
$form
.
find
(
'
textarea.markdown-area
'
).
focus
();
$form
.
find
(
'
.md-preview-holder
'
).
hide
();
markdownPreview
.
hideReferencedCommands
(
$form
);
});
$
(
document
).
on
(
'
markdown-preview:toggle
'
,
function
(
e
,
keyboardEvent
)
{
...
...
app/controllers/concerns/markdown_preview.rb
deleted
100644 → 0
View file @
5c29411e
module
MarkdownPreview
private
def
render_markdown_preview
(
text
,
markdown_context
=
{})
render
json:
{
body:
view_context
.
markdown
(
text
,
markdown_context
),
references:
{
users:
preview_referenced_users
(
text
)
}
}
end
def
preview_referenced_users
(
text
)
extractor
=
Gitlab
::
ReferenceExtractor
.
new
(
@project
,
current_user
)
extractor
.
analyze
(
text
,
author:
current_user
)
extractor
.
users
.
map
(
&
:username
)
end
end
app/controllers/projects/wikis_controller.rb
View file @
5fd0d376
class
Projects::WikisController
<
Projects
::
ApplicationController
include
MarkdownPreview
before_action
:authorize_read_wiki!
before_action
:authorize_create_wiki!
,
only:
[
:edit
,
:create
,
:history
]
before_action
:authorize_admin_wiki!
,
only: :destroy
...
...
@@ -103,9 +101,14 @@ class Projects::WikisController < Projects::ApplicationController
end
def
preview_markdown
context
=
{
pipeline: :wiki
,
project_wiki:
@project_wiki
,
page_slug:
params
[
:id
]
}
render_markdown_preview
(
params
[
:text
],
context
)
result
=
PreviewMarkdownService
.
new
(
@project
,
current_user
,
params
).
execute
render
json:
{
body:
view_context
.
markdown
(
result
[
:text
],
pipeline: :wiki
,
project_wiki:
@project_wiki
,
page_slug:
params
[
:id
]),
references:
{
users:
result
[
:users
]
}
}
end
private
...
...
app/controllers/projects_controller.rb
View file @
5fd0d376
class
ProjectsController
<
Projects
::
ApplicationController
include
IssuableCollections
include
ExtractsPath
include
MarkdownPreview
before_action
:authenticate_user!
,
except:
[
:index
,
:show
,
:activity
,
:refs
]
before_action
:project
,
except:
[
:index
,
:new
,
:create
]
...
...
@@ -241,7 +240,15 @@ class ProjectsController < Projects::ApplicationController
end
def
preview_markdown
render_markdown_preview
(
params
[
:text
])
result
=
PreviewMarkdownService
.
new
(
@project
,
current_user
,
params
).
execute
render
json:
{
body:
view_context
.
markdown
(
result
[
:text
]),
references:
{
users:
result
[
:users
],
commands:
view_context
.
markdown
(
result
[
:commands
])
}
}
end
private
...
...
app/controllers/snippets_controller.rb
View file @
5fd0d376
...
...
@@ -3,7 +3,6 @@ class SnippetsController < ApplicationController
include
ToggleAwardEmoji
include
SpammableActions
include
SnippetsActions
include
MarkdownPreview
include
RendersBlob
before_action
:snippet
,
only:
[
:show
,
:edit
,
:destroy
,
:update
,
:raw
,
:download
]
...
...
@@ -98,7 +97,14 @@ class SnippetsController < ApplicationController
end
def
preview_markdown
render_markdown_preview
(
params
[
:text
],
skip_project_check:
true
)
result
=
PreviewMarkdownService
.
new
(
@project
,
current_user
,
params
).
execute
render
json:
{
body:
view_context
.
markdown
(
result
[
:text
],
skip_project_check:
true
),
references:
{
users:
result
[
:users
]
}
}
end
protected
...
...
app/helpers/gitlab_routing_helper.rb
View file @
5fd0d376
...
...
@@ -122,6 +122,10 @@ module GitlabRoutingHelper
namespace_project_snippet_url
(
entity
.
project
.
namespace
,
entity
.
project
,
entity
,
*
args
)
end
def
preview_markdown_path
(
project
,
*
args
)
preview_markdown_namespace_project_path
(
project
.
namespace
,
project
,
*
args
)
end
def
toggle_subscription_path
(
entity
,
*
args
)
if
entity
.
is_a?
(
Issue
)
toggle_subscription_namespace_project_issue_path
(
entity
.
project
.
namespace
,
entity
.
project
,
entity
)
...
...
app/services/preview_markdown_service.rb
0 → 100644
View file @
5fd0d376
class
PreviewMarkdownService
<
BaseService
def
execute
text
,
commands
=
explain_slash_commands
(
params
[
:text
])
users
=
find_user_references
(
text
)
success
(
text:
text
,
users:
users
,
commands:
commands
.
join
(
' '
)
)
end
private
def
explain_slash_commands
(
text
)
return
text
,
[]
unless
%w(Issue MergeRequest)
.
include?
(
commands_target_type
)
slash_commands_service
=
SlashCommands
::
InterpretService
.
new
(
project
,
current_user
)
slash_commands_service
.
explain
(
text
,
find_commands_target
)
end
def
find_user_references
(
text
)
extractor
=
Gitlab
::
ReferenceExtractor
.
new
(
project
,
current_user
)
extractor
.
analyze
(
text
,
author:
current_user
)
extractor
.
users
.
map
(
&
:username
)
end
def
find_commands_target
if
commands_target_id
.
present?
finder
=
commands_target_type
==
'Issue'
?
IssuesFinder
:
MergeRequestsFinder
finder
.
new
(
current_user
,
project_id:
project
.
id
).
find
(
commands_target_id
)
else
collection
=
commands_target_type
==
'Issue'
?
project
.
issues
:
project
.
merge_requests
collection
.
build
end
end
def
commands_target_type
params
[
:slash_commands_target_type
]
end
def
commands_target_id
params
[
:slash_commands_target_id
]
end
end
app/services/slash_commands/interpret_service.rb
View file @
5fd0d376
This diff is collapsed.
Click to expand it.
app/views/groups/milestones/new.html.haml
View file @
5fd0d376
...
...
@@ -26,7 +26,7 @@
.form-group.milestone-description
=
f
.
label
:description
,
"Description"
,
class:
"control-label"
.col-sm-10
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
''
}
do
=
render
'projects/zen'
,
f:
f
,
attr: :description
,
classes:
'note-textarea'
,
placeholder:
'Write milestone description...'
.clearfix
.error-alert
...
...
app/views/layouts/project.html.haml
View file @
5fd0d376
...
...
@@ -5,14 +5,9 @@
-
content_for
:project_javascripts
do
-
project
=
@target_project
||
@project
-
if
@project_wiki
&&
@page
-
preview_markdown_path
=
namespace_project_wiki_preview_markdown_path
(
project
.
namespace
,
project
,
@page
.
slug
)
-
else
-
preview_markdown_path
=
preview_markdown_namespace_project_path
(
project
.
namespace
,
project
)
-
if
current_user
:javascript
window
.
project_uploads_path
=
"
#{
namespace_project_uploads_path
project
.
namespace
,
project
}
"
;
window
.
preview_markdown_path
=
"
#{
preview_markdown_path
}
"
;
-
content_for
:header_content
do
.js-dropdown-menu-projects
...
...
app/views/projects/_md_preview.html.haml
View file @
5fd0d376
-
referenced_users
=
local_assigns
.
fetch
(
:referenced_users
,
nil
)
.md-area
.md-header
%ul
.nav-links.clearfix
...
...
@@ -28,9 +30,10 @@
.md-write-holder
=
yield
.md.md-preview-holder.js-md-preview.hide
{
class:
(
preview_class
if
defined?
(
preview_class
))
}
.md.md-preview-holder.js-md-preview.hide.md-preview
{
data:
{
url:
url
}
}
.referenced-commands.hide
-
if
defined?
(
referenced_users
)
&&
referenced_users
-
if
referenced_users
.referenced-users.hide
%span
=
icon
(
"exclamation-triangle"
)
...
...
app/views/projects/milestones/_form.html.haml
View file @
5fd0d376
...
...
@@ -9,7 +9,7 @@
.form-group.milestone-description
=
f
.
label
:description
,
"Description"
,
class:
"control-label"
.col-sm-10
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
preview_markdown_path
(
@project
)
}
do
=
render
'projects/zen'
,
f:
f
,
attr: :description
,
classes:
'note-textarea'
,
placeholder:
'Write milestone description...'
=
render
'projects/notes/hints'
.clearfix
...
...
app/views/projects/notes/_edit_form.html.haml
View file @
5fd0d376
...
...
@@ -2,7 +2,7 @@
=
form_tag
'#'
,
method: :put
,
class:
'edit-note common-note-form js-quick-submit'
do
=
hidden_field_tag
:target_id
,
''
,
class:
'js-form-target-id'
=
hidden_field_tag
:target_type
,
''
,
class:
'js-form-target-type'
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
'md-preview'
,
referenced_users:
true
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
preview_markdown_path
(
project
)
,
referenced_users:
true
}
do
=
render
'projects/zen'
,
attr:
'note[note]'
,
classes:
'note-textarea js-note-text js-task-list-field'
,
placeholder:
"Write a comment or drag your files here..."
=
render
'projects/notes/hints'
...
...
app/views/projects/notes/_form.html.haml
View file @
5fd0d376
-
supports_slash_commands
=
note_supports_slash_commands?
(
@note
)
-
if
supports_slash_commands
-
preview_url
=
preview_markdown_path
(
@project
,
slash_commands_target_type:
@note
.
noteable_type
,
slash_commands_target_id:
@note
.
noteable_id
)
-
else
-
preview_url
=
preview_markdown_path
(
@project
)
=
form_for
[
@project
.
namespace
.
becomes
(
Namespace
),
@project
,
@note
],
remote:
true
,
html:
{
:'data-type'
=>
'json'
,
multipart:
true
,
id:
nil
,
class:
"new-note js-new-note-form js-quick-submit common-note-form"
,
"data-noteable-iid"
=>
@note
.
noteable
.
try
(
:iid
),
},
authenticity_token:
true
do
|
f
|
=
hidden_field_tag
:view
,
diff_view
...
...
@@ -18,7 +22,7 @@
-# DiffNote
=
f
.
hidden_field
:position
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
,
referenced_users:
true
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
preview_url
,
referenced_users:
true
}
do
=
render
'projects/zen'
,
f:
f
,
attr: :note
,
classes:
'note-textarea js-note-text'
,
...
...
app/views/projects/notes/_notes_with_form.html.haml
View file @
5fd0d376
%ul
#notes-list
.notes.main-notes-list.timeline
=
render
"shared/notes/notes"
=
render
'projects/notes/edit_form'
=
render
'projects/notes/edit_form'
,
project:
@project
%ul
.notes.notes-form.timeline
%li
.timeline-entry
...
...
app/views/projects/releases/edit.html.haml
View file @
5fd0d376
...
...
@@ -11,7 +11,7 @@
=
form_for
(
@release
,
method: :put
,
url:
namespace_project_tag_release_path
(
@project
.
namespace
,
@project
,
@tag
.
name
),
html:
{
class:
'form-horizontal common-note-form release-form js-quick-submit'
})
do
|
f
|
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
,
referenced_users:
true
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
preview_markdown_path
(
@project
)
,
referenced_users:
true
}
do
=
render
'projects/zen'
,
f:
f
,
attr: :description
,
classes:
'note-textarea'
,
placeholder:
"Write your release notes or drag files here..."
=
render
'projects/notes/hints'
.error-alert
...
...
app/views/projects/tags/new.html.haml
View file @
5fd0d376
...
...
@@ -28,7 +28,7 @@
.form-group
=
label_tag
:release_description
,
'Release notes'
,
class:
'control-label'
.col-sm-10
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
,
referenced_users:
true
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
preview_markdown_path
(
@project
)
,
referenced_users:
true
}
do
=
render
'projects/zen'
,
attr: :release_description
,
classes:
'note-textarea'
,
placeholder:
"Write your release notes or drag files here..."
=
render
'projects/notes/hints'
.help-block
Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
...
...
app/views/projects/wikis/_form.html.haml
View file @
5fd0d376
...
...
@@ -12,7 +12,7 @@
.form-group
=
f
.
label
:content
,
class:
'control-label'
.col-sm-10
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
namespace_project_wiki_preview_markdown_path
(
@project
.
namespace
,
@project
,
@page
.
slug
)
}
do
=
render
'projects/zen'
,
f:
f
,
attr: :content
,
classes:
'note-textarea'
,
placeholder:
'Write your content or drag files here...'
=
render
'projects/notes/hints'
...
...
app/views/shared/issuable/_form.html.haml
View file @
5fd0d376
...
...
@@ -17,7 +17,7 @@
=
render
'shared/issuable/form/template_selector'
,
issuable:
issuable
=
render
'shared/issuable/form/title'
,
issuable:
issuable
,
form:
form
,
has_wip_commits:
commits
&&
commits
.
detect
(
&
:work_in_progress?
)
=
render
'shared/issuable/form/description'
,
issuable:
issuable
,
form:
form
=
render
'shared/issuable/form/description'
,
issuable:
issuable
,
form:
form
,
project:
project
-
if
issuable
.
respond_to?
(
:confidential
)
.form-group
...
...
app/views/shared/issuable/form/_description.html.haml
View file @
5fd0d376
-
project
=
local_assigns
.
fetch
(
:project
)
-
issuable
=
local_assigns
.
fetch
(
:issuable
)
-
form
=
local_assigns
.
fetch
(
:form
)
-
supports_slash_commands
=
issuable
.
new_record?
-
if
supports_slash_commands
-
preview_url
=
preview_markdown_path
(
project
,
slash_commands_target_type:
issuable
.
class
.
name
)
-
else
-
preview_url
=
preview_markdown_path
(
project
)
.form-group.detail-page-description
=
form
.
label
:description
,
'Description'
,
class:
'control-label'
.col-sm-10
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
,
referenced_users:
true
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
url:
preview_url
,
referenced_users:
true
}
do
=
render
'projects/zen'
,
f:
form
,
attr: :description
,
classes:
'note-textarea'
,
placeholder:
"Write a comment or drag your files here..."
,
supports_slash_commands:
!
issuable
.
persisted?
=
render
'projects/notes/hints'
,
supports_slash_commands:
!
issuable
.
persisted?
supports_slash_commands:
supports_slash_commands
=
render
'projects/notes/hints'
,
supports_slash_commands:
supports_slash_commands
.clearfix
.error-alert
changelogs/unreleased/preview-separate-slash-commands.yml
0 → 100644
View file @
5fd0d376
---
title
:
Display slash commands outcome when previewing Markdown
merge_request
:
10054
author
:
Rares Sfirlogea
lib/gitlab/slash_commands/command_definition.rb
View file @
5fd0d376
module
Gitlab
module
SlashCommands
class
CommandDefinition
attr_accessor
:name
,
:aliases
,
:description
,
:params
,
:condition_block
,
:action_block
attr_accessor
:name
,
:aliases
,
:description
,
:explanation
,
:params
,
:condition_block
,
:parse_params_block
,
:action_block
def
initialize
(
name
,
attributes
=
{})
@name
=
name
@aliases
=
attributes
[
:aliases
]
||
[]
@description
=
attributes
[
:description
]
||
''
@params
=
attributes
[
:params
]
||
[]
@aliases
=
attributes
[
:aliases
]
||
[]
@description
=
attributes
[
:description
]
||
''
@explanation
=
attributes
[
:explanation
]
||
''
@params
=
attributes
[
:params
]
||
[]
@condition_block
=
attributes
[
:condition_block
]
@action_block
=
attributes
[
:action_block
]
@parse_params_block
=
attributes
[
:parse_params_block
]
@action_block
=
attributes
[
:action_block
]
end
def
all_names
...
...
@@ -28,14 +31,20 @@ module Gitlab
context
.
instance_exec
(
&
condition_block
)
end
def
explain
(
context
,
opts
,
arg
)
return
unless
available?
(
opts
)
if
explanation
.
respond_to?
(
:call
)
execute_block
(
explanation
,
context
,
arg
)
else
explanation
end
end
def
execute
(
context
,
opts
,
arg
)
return
if
noop?
||
!
available?
(
opts
)
if
arg
.
present?
context
.
instance_exec
(
arg
,
&
action_block
)
elsif
action_block
.
arity
==
0
context
.
instance_exec
(
&
action_block
)
end
execute_block
(
action_block
,
context
,
arg
)
end
def
to_h
(
opts
)
...
...
@@ -52,6 +61,23 @@ module Gitlab
params:
params
}
end
private
def
execute_block
(
block
,
context
,
arg
)
if
arg
.
present?
parsed
=
parse_params
(
arg
,
context
)
context
.
instance_exec
(
parsed
,
&
block
)
elsif
block
.
arity
==
0
context
.
instance_exec
(
&
block
)
end
end
def
parse_params
(
arg
,
context
)
return
arg
unless
parse_params_block
context
.
instance_exec
(
arg
,
&
parse_params_block
)
end
end
end
end
lib/gitlab/slash_commands/dsl.rb
View file @
5fd0d376
...
...
@@ -44,6 +44,22 @@ module Gitlab
@params
=
params
end
# Allows to give an explanation of what the command will do when
# executed. This explanation is shown when rendering the Markdown
# preview.
#
# Example:
#
# explanation do |arguments|
# "Adds label(s) #{arguments.join(' ')}"
# end
# command :command_key do |arguments|
# # Awesome code block
# end
def
explanation
(
text
=
''
,
&
block
)
@explanation
=
block_given?
?
block
:
text
end
# Allows to define conditions that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
# It accepts a block that will be evaluated with the context given to
...
...
@@ -61,6 +77,24 @@ module Gitlab
@condition_block
=
block
end
# Allows to perform initial parsing of parameters. The result is passed
# both to `command` and `explanation` blocks, instead of the raw
# parameters.
# It accepts a block that will be evaluated with the context given to
# `CommandDefintion#to_h`.
#
# Example:
#
# parse_params do |raw|
# raw.strip
# end
# command :command_key do |parsed|
# # Awesome code block
# end
def
parse_params
(
&
block
)
@parse_params_block
=
block
end
# Registers a new command which is recognizeable from body of email or
# comment.
# It accepts aliases and takes a block.
...
...
@@ -75,11 +109,13 @@ module Gitlab
definition
=
CommandDefinition
.
new
(
name
,
aliases:
aliases
,
description:
@description
,
params:
@params
,
condition_block:
@condition_block
,
action_block:
block
aliases:
aliases
,
description:
@description
,
explanation:
@explanation
,
params:
@params
,
condition_block:
@condition_block
,
parse_params_block:
@parse_params_block
,
action_block:
block
)
self
.
command_definitions
<<
definition
...
...
@@ -89,8 +125,14 @@ module Gitlab
end
@description
=
nil
@explanation
=
nil
@params
=
nil
@condition_block
=
nil
@parse_params_block
=
nil
end
def
definition_by_name
(
name
)
command_definitions_by_name
[
name
.
to_sym
]
end
end
end
...
...
spec/lib/gitlab/slash_commands/command_definition_spec.rb
View file @
5fd0d376
...
...
@@ -167,6 +167,58 @@ describe Gitlab::SlashCommands::CommandDefinition do
end
end
end
context
'when the command defines parse_params block'
do
before
do
subject
.
parse_params_block
=
->
(
raw
)
{
raw
.
strip
}
subject
.
action_block
=
->
(
parsed
)
{
self
.
received_arg
=
parsed
}
end
it
'executes the command passing the parsed param'
do
subject
.
execute
(
context
,
{},
'something '
)
expect
(
context
.
received_arg
).
to
eq
(
'something'
)
end
end
end
end
end
describe
'#explain'
do
context
'when the command is not available'
do
before
do
subject
.
condition_block
=
proc
{
false
}
subject
.
explanation
=
'Explanation'
end
it
'returns nil'
do
result
=
subject
.
explain
({},
{},
nil
)
expect
(
result
).
to
be_nil
end
end
context
'when the explanation is a static string'
do
before
do
subject
.
explanation
=
'Explanation'
end
it
'returns this static string'
do
result
=
subject
.
explain
({},
{},
nil
)
expect
(
result
).
to
eq
'Explanation'
end
end
context
'when the explanation is dynamic'
do
before
do
subject
.
explanation
=
proc
{
|
arg
|
"Dynamic
#{
arg
}
"
}
end
it
'invokes the proc'
do
result
=
subject
.
explain
({},
{},
'explanation'
)
expect
(
result
).
to
eq
'Dynamic explanation'
end
end
end
...
...
spec/lib/gitlab/slash_commands/dsl_spec.rb
View file @
5fd0d376
...
...
@@ -11,67 +11,99 @@ describe Gitlab::SlashCommands::Dsl do
end
params
'The first argument'
command
:one_arg
,
:once
,
:first
do
|
arg1
|
arg1
explanation
'Static explanation'
command
:explanation_with_aliases
,
:once
,
:first
do
|
arg
|
arg
end
desc
do
"A dynamic description for
#{
noteable
.
upcase
}
"
end
params
'The first argument'
,
'The second argument'
command
:
two_args
do
|
arg1
,
arg2
|
[
arg1
,
arg2
]
command
:
dynamic_description
do
|
args
|
args
.
split
end
command
:cc
explanation
do
|
arg
|
"Action does something with
#{
arg
}
"
end
condition
do
project
==
'foo'
end
command
:cond_action
do
|
arg
|
arg
end
parse_params
do
|
raw_arg
|
raw_arg
.
strip
end
command
:with_params_parsing
do
|
parsed
|
parsed
end
end
end
describe
'.command_definitions'
do
it
'returns an array with commands definitions'
do
no_args_def
,
one_arg_def
,
two_args_def
,
cc_def
,
cond_action_def
=
DummyClass
.
command_definitions
no_args_def
,
explanation_with_aliases_def
,
dynamic_description_def
,
cc_def
,
cond_action_def
,
with_params_parsing_def
=
DummyClass
.
command_definitions
expect
(
no_args_def
.
name
).
to
eq
(
:no_args
)
expect
(
no_args_def
.
aliases
).
to
eq
([
:none
])
expect
(
no_args_def
.
description
).
to
eq
(
'A command with no args'
)
expect
(
no_args_def
.
explanation
).
to
eq
(
''
)
expect
(
no_args_def
.
params
).
to
eq
([])
expect
(
no_args_def
.
condition_block
).
to
be_nil
expect
(
no_args_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
no_args_def
.
parse_params_block
).
to
be_nil
expect
(
one_arg_def
.
name
).
to
eq
(
:one_arg
)
expect
(
one_arg_def
.
aliases
).
to
eq
([
:once
,
:first
])
expect
(
one_arg_def
.
description
).
to
eq
(
''
)
expect
(
one_arg_def
.
params
).
to
eq
([
'The first argument'
])
expect
(
one_arg_def
.
condition_block
).
to
be_nil
expect
(
one_arg_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
explanation_with_aliases_def
.
name
).
to
eq
(
:explanation_with_aliases
)
expect
(
explanation_with_aliases_def
.
aliases
).
to
eq
([
:once
,
:first
])
expect
(
explanation_with_aliases_def
.
description
).
to
eq
(
''
)
expect
(
explanation_with_aliases_def
.
explanation
).
to
eq
(
'Static explanation'
)
expect
(
explanation_with_aliases_def
.
params
).
to
eq
([
'The first argument'
])
expect
(
explanation_with_aliases_def
.
condition_block
).
to
be_nil
expect
(
explanation_with_aliases_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
explanation_with_aliases_def
.
parse_params_block
).
to
be_nil
expect
(
two_args_def
.
name
).
to
eq
(
:two_args
)
expect
(
two_args_def
.
aliases
).
to
eq
([])
expect
(
two_args_def
.
to_h
(
noteable:
"issue"
)[
:description
]).
to
eq
(
'A dynamic description for ISSUE'
)
expect
(
two_args_def
.
params
).
to
eq
([
'The first argument'
,
'The second argument'
])
expect
(
two_args_def
.
condition_block
).
to
be_nil
expect
(
two_args_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
dynamic_description_def
.
name
).
to
eq
(
:dynamic_description
)
expect
(
dynamic_description_def
.
aliases
).
to
eq
([])
expect
(
dynamic_description_def
.
to_h
(
noteable:
'issue'
)[
:description
]).
to
eq
(
'A dynamic description for ISSUE'
)
expect
(
dynamic_description_def
.
explanation
).
to
eq
(
''
)
expect
(
dynamic_description_def
.
params
).
to
eq
([
'The first argument'
,
'The second argument'
])
expect
(
dynamic_description_def
.
condition_block
).
to
be_nil
expect
(
dynamic_description_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
dynamic_description_def
.
parse_params_block
).
to
be_nil
expect
(
cc_def
.
name
).
to
eq
(
:cc
)
expect
(
cc_def
.
aliases
).
to
eq
([])
expect
(
cc_def
.
description
).
to
eq
(
''
)
expect
(
cc_def
.
explanation
).
to
eq
(
''
)
expect
(
cc_def
.
params
).
to
eq
([])
expect
(
cc_def
.
condition_block
).
to
be_nil
expect
(
cc_def
.
action_block
).
to
be_nil
expect
(
cc_def
.
parse_params_block
).
to
be_nil
expect
(
cond_action_def
.
name
).
to
eq
(
:cond_action
)
expect
(
cond_action_def
.
aliases
).
to
eq
([])
expect
(
cond_action_def
.
description
).
to
eq
(
''
)
expect
(
cond_action_def
.
explanation
).
to
be_a_kind_of
(
Proc
)
expect
(
cond_action_def
.
params
).
to
eq
([])
expect
(
cond_action_def
.
condition_block
).
to
be_a_kind_of
(
Proc
)
expect
(
cond_action_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
cond_action_def
.
parse_params_block
).
to
be_nil
expect
(
with_params_parsing_def
.
name
).
to
eq
(
:with_params_parsing
)
expect
(
with_params_parsing_def
.
aliases
).
to
eq
([])
expect
(
with_params_parsing_def
.
description
).
to
eq
(
''
)
expect
(
with_params_parsing_def
.
explanation
).
to
eq
(
''
)
expect
(
with_params_parsing_def
.
params
).
to
eq
([])
expect
(
with_params_parsing_def
.
condition_block
).
to
be_nil
expect
(
with_params_parsing_def
.
action_block
).
to
be_a_kind_of
(
Proc
)
expect
(
with_params_parsing_def
.
parse_params_block
).
to
be_a_kind_of
(
Proc
)
end
end
end
spec/services/preview_markdown_service_spec.rb
0 → 100644
View file @
5fd0d376
require
'spec_helper'
describe
PreviewMarkdownService
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:empty_project
)
}
before
do
project
.
add_developer
(
user
)
end
describe
'user references'
do
let
(
:params
)
{
{
text:
"Take a look
#{
user
.
to_reference
}
"
}
}
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
it
'returns users referenced in text'
do
result
=
service
.
execute
expect
(
result
[
:users
]).
to
eq
[
user
.
username
]
end
end
context
'new note with slash commands'
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:params
)
do
{
text:
"Please do it
\n
/assign
#{
user
.
to_reference
}
"
,
slash_commands_target_type:
'Issue'
,
slash_commands_target_id:
issue
.
id
}
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
it
'removes slash commands from text'
do
result
=
service
.
execute
expect
(
result
[
:text
]).
to
eq
'Please do it'
end
it
'explains slash commands effect'
do
result
=
service
.
execute
expect
(
result
[
:commands
]).
to
eq
"Assigns
#{
user
.
to_reference
}
."
end
end
context
'merge request description'
do
let
(
:params
)
do
{
text:
"My work
\n
/estimate 2y"
,
slash_commands_target_type:
'MergeRequest'
}
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
it
'removes slash commands from text'
do
result
=
service
.
execute
expect
(
result
[
:text
]).
to
eq
'My work'
end
it
'explains slash commands effect'
do
result
=
service
.
execute
expect
(
result
[
:commands
]).
to
eq
'Sets time estimate to 2y.'
end
end
end
spec/services/slash_commands/interpret_service_spec.rb
View file @
5fd0d376
...
...
@@ -825,4 +825,222 @@ describe SlashCommands::InterpretService, services: true do
end
end
end
describe
'#explain'
do
let
(
:service
)
{
described_class
.
new
(
project
,
developer
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
describe
'close command'
do
let
(
:content
)
{
'/close'
}
it
'includes issuable name'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Closes this issue.'
])
end
end
describe
'reopen command'
do
let
(
:content
)
{
'/reopen'
}
let
(
:merge_request
)
{
create
(
:merge_request
,
:closed
,
source_project:
project
)
}
it
'includes issuable name'
do
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Reopens this merge request.'
])
end
end
describe
'title command'
do
let
(
:content
)
{
'/title This is new title'
}
it
'includes new title'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Changes the title to "This is new title".'
])
end
end
describe
'assign command'
do
let
(
:content
)
{
"/assign @
#{
developer
.
username
}
do it!"
}
it
'includes only the user reference'
do
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
"Assigns @
#{
developer
.
username
}
."
])
end
end
describe
'unassign command'
do
let
(
:content
)
{
'/unassign'
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
assignee:
developer
)
}
it
'includes current assignee reference'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
"Removes assignee @
#{
developer
.
username
}
."
])
end
end
describe
'milestone command'
do
let
(
:content
)
{
'/milestone %wrong-milestone'
}
let!
(
:milestone
)
{
create
(
:milestone
,
project:
project
,
title:
'9.10'
)
}
it
'is empty when milestone reference is wrong'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([])
end
end
describe
'remove milestone command'
do
let
(
:content
)
{
'/remove_milestone'
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
milestone:
milestone
)
}
it
'includes current milestone name'
do
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Removes %"9.10" milestone.'
])
end
end
describe
'label command'
do
let
(
:content
)
{
'/label ~missing'
}
let!
(
:label
)
{
create
(
:label
,
project:
project
)
}
it
'is empty when there are no correct labels'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([])
end
end
describe
'unlabel command'
do
let
(
:content
)
{
'/unlabel'
}
it
'says all labels if no parameter provided'
do
merge_request
.
update!
(
label_ids:
[
bug
.
id
])
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Removes all labels.'
])
end
end
describe
'relabel command'
do
let
(
:content
)
{
'/relabel Bug'
}
let!
(
:bug
)
{
create
(
:label
,
project:
project
,
title:
'Bug'
)
}
let
(
:feature
)
{
create
(
:label
,
project:
project
,
title:
'Feature'
)
}
it
'includes label name'
do
issue
.
update!
(
label_ids:
[
feature
.
id
])
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
"Replaces all labels with ~
#{
bug
.
id
}
label."
])
end
end
describe
'subscribe command'
do
let
(
:content
)
{
'/subscribe'
}
it
'includes issuable name'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Subscribes to this issue.'
])
end
end
describe
'unsubscribe command'
do
let
(
:content
)
{
'/unsubscribe'
}
it
'includes issuable name'
do
merge_request
.
subscribe
(
developer
,
project
)
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Unsubscribes from this merge request.'
])
end
end
describe
'due command'
do
let
(
:content
)
{
'/due April 1st 2016'
}
it
'includes the date'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Sets the due date to Apr 1, 2016.'
])
end
end
describe
'wip command'
do
let
(
:content
)
{
'/wip'
}
it
'includes the new status'
do
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Marks this merge request as Work In Progress.'
])
end
end
describe
'award command'
do
let
(
:content
)
{
'/award :confetti_ball: '
}
it
'includes the emoji'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Toggles :confetti_ball: emoji award.'
])
end
end
describe
'estimate command'
do
let
(
:content
)
{
'/estimate 79d'
}
it
'includes the formatted duration'
do
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Sets time estimate to 3mo 3w 4d.'
])
end
end
describe
'spend command'
do
let
(
:content
)
{
'/spend -120m'
}
it
'includes the formatted duration and proper verb'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Substracts 2h spent time.'
])
end
end
describe
'target branch command'
do
let
(
:content
)
{
'/target_branch my-feature '
}
it
'includes the branch name'
do
_
,
explanations
=
service
.
explain
(
content
,
merge_request
)
expect
(
explanations
).
to
eq
([
'Sets target branch to my-feature.'
])
end
end
describe
'board move command'
do
let
(
:content
)
{
'/board_move ~bug'
}
let!
(
:bug
)
{
create
(
:label
,
project:
project
,
title:
'bug'
)
}
let!
(
:board
)
{
create
(
:board
,
project:
project
)
}
it
'includes the label name'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
"Moves issue to ~
#{
bug
.
id
}
column in the board."
])
end
end
# EE-specific tests
describe
'weight command'
do
let
(
:content
)
{
'/weight 4'
}
it
'includes the number'
do
_
,
explanations
=
service
.
explain
(
content
,
issue
)
expect
(
explanations
).
to
eq
([
'Sets weight to 4.'
])
end
end
end
end
spec/support/features/issuable_slash_commands_shared_examples.rb
View file @
5fd0d376
...
...
@@ -257,4 +257,19 @@ shared_examples 'issuable record that supports slash commands in its description
end
end
end
describe
"preview of note on
#{
issuable_type
}
"
do
it
'removes slash commands from note and explains them'
do
visit
public_send
(
"namespace_project_
#{
issuable_type
}
_path"
,
project
.
namespace
,
project
,
issuable
)
page
.
within
(
'.js-main-target-form'
)
do
fill_in
'note[note]'
,
with:
"Awesome!
\n
/assign @bob "
click_on
'Preview'
expect
(
page
).
to
have_content
'Awesome!'
expect
(
page
).
not_to
have_content
'/assign @bob'
expect
(
page
).
to
have_content
'Assigns @bob.'
end
end
end
end
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