Support descriptions for snippets
This commit is contained in:
parent
8039b9c3c6
commit
4464c22d6d
34 changed files with 459 additions and 37 deletions
|
@ -218,6 +218,16 @@ import ShortcutsBlob from './shortcuts_blob';
|
|||
new gl.GLForm($('.tag-form'));
|
||||
new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs);
|
||||
break;
|
||||
case 'projects:snippets:new':
|
||||
case 'projects:snippets:edit':
|
||||
case 'projects:snippets:create':
|
||||
case 'projects:snippets:update':
|
||||
case 'snippets:new':
|
||||
case 'snippets:edit':
|
||||
case 'snippets:create':
|
||||
case 'snippets:update':
|
||||
new gl.GLForm($('.snippet-form'));
|
||||
break;
|
||||
case 'projects:releases:edit':
|
||||
new ZenMode();
|
||||
new gl.GLForm($('.release-form'));
|
||||
|
|
|
@ -5,7 +5,7 @@ import './preview_markdown';
|
|||
|
||||
window.DropzoneInput = (function() {
|
||||
function DropzoneInput(form) {
|
||||
var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile;
|
||||
var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile, addFileToForm;
|
||||
Dropzone.autoDiscover = false;
|
||||
divHover = '<div class="div-dropzone-hover"></div>';
|
||||
iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
|
||||
|
@ -71,6 +71,7 @@ window.DropzoneInput = (function() {
|
|||
pasteText(response.link.markdown, shouldPad);
|
||||
// Show 'Attach a file' link only when all files have been uploaded.
|
||||
if (!processingFileCount) $attachButton.removeClass('hide');
|
||||
addFileToForm(response.link.url);
|
||||
},
|
||||
error: function(file, errorMessage = 'Attaching the file failed.', xhr) {
|
||||
// If 'error' event is fired by dropzone, the second parameter is error message.
|
||||
|
@ -197,6 +198,10 @@ window.DropzoneInput = (function() {
|
|||
return formTextarea.trigger('input');
|
||||
};
|
||||
|
||||
addFileToForm = function(path) {
|
||||
$(form).append('<input type="hidden" name="files[]" value="' + path + '">');
|
||||
};
|
||||
|
||||
getFilename = function(e) {
|
||||
var value;
|
||||
if (window.clipboardData && window.clipboardData.getData) {
|
||||
|
|
|
@ -107,6 +107,6 @@ class Projects::SnippetsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def snippet_params
|
||||
params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
|
||||
params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,6 +45,8 @@ class SnippetsController < ApplicationController
|
|||
|
||||
@snippet = CreateSnippetService.new(nil, current_user, create_params).execute
|
||||
|
||||
move_temporary_files if params[:files]
|
||||
|
||||
recaptcha_check_with_fallback { render :new }
|
||||
end
|
||||
|
||||
|
@ -124,6 +126,12 @@ class SnippetsController < ApplicationController
|
|||
end
|
||||
|
||||
def snippet_params
|
||||
params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
|
||||
params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description)
|
||||
end
|
||||
|
||||
def move_temporary_files
|
||||
params[:files].each do |file|
|
||||
FileMover.new(file, @snippet).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,8 @@ class UploadsController < ApplicationController
|
|||
private
|
||||
|
||||
def find_model
|
||||
return nil unless params[:id]
|
||||
|
||||
return render_404 unless upload_model && upload_mount
|
||||
|
||||
@model = upload_model.find(params[:id])
|
||||
|
@ -33,6 +35,8 @@ class UploadsController < ApplicationController
|
|||
end
|
||||
|
||||
def authorize_create_access!
|
||||
return unless model
|
||||
|
||||
# for now we support only personal snippets comments
|
||||
authorized = can?(current_user, :comment_personal_snippet, model)
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ module GitlabRoutingHelper
|
|||
|
||||
def preview_markdown_path(project, *args)
|
||||
if @snippet.is_a?(PersonalSnippet)
|
||||
preview_markdown_snippet_path(@snippet)
|
||||
preview_markdown_snippets_path
|
||||
else
|
||||
preview_markdown_namespace_project_path(project.namespace, project, *args)
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ class Snippet < ActiveRecord::Base
|
|||
include Spammable
|
||||
|
||||
cache_markdown_field :title, pipeline: :single_line
|
||||
cache_markdown_field :description
|
||||
cache_markdown_field :content
|
||||
|
||||
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with snippets.
|
||||
|
|
48
app/uploaders/file_mover.rb
Normal file
48
app/uploaders/file_mover.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
class FileMover
|
||||
attr_reader :secret, :file_name, :model
|
||||
|
||||
def initialize(file_path, model, update_field = :description)
|
||||
@secret = File.split(File.dirname(file_path)).last
|
||||
@file_name = File.basename(file_path)
|
||||
@model = model
|
||||
end
|
||||
|
||||
def execute
|
||||
move
|
||||
update_markdown
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def move
|
||||
FileUtils.mkdir_p(file_path)
|
||||
FileUtils.move(temp_file_path, file_path)
|
||||
end
|
||||
|
||||
def update_markdown(field = :description)
|
||||
updated_text = model.send(field).sub(temp_file_uploader.to_markdown, uploader.to_markdown)
|
||||
model.update_attribute(field, updated_text)
|
||||
end
|
||||
|
||||
def temp_file_path
|
||||
temp_file_uploader.retrieve_from_store!(file_name)
|
||||
|
||||
temp_file_uploader.file.path
|
||||
end
|
||||
|
||||
def file_path
|
||||
return @file_path if @file_path
|
||||
|
||||
uploader.retrieve_from_store!(file_name)
|
||||
|
||||
@file_path = uploader.file.path
|
||||
end
|
||||
|
||||
def uploader
|
||||
@uploader ||= PersonalFileUploader.new(model, secret)
|
||||
end
|
||||
|
||||
def temp_file_uploader
|
||||
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret)
|
||||
end
|
||||
end
|
|
@ -10,6 +10,10 @@ class PersonalFileUploader < FileUploader
|
|||
end
|
||||
|
||||
def self.model_path(model)
|
||||
File.join("/#{base_dir}", model.class.to_s.underscore, model.id.to_s)
|
||||
if model
|
||||
File.join("/#{base_dir}", model.class.to_s.underscore, model.id.to_s)
|
||||
else
|
||||
File.join("/#{base_dir}", 'temp')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
- header_title "Snippets", snippets_path
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
- if @snippet&.persisted? && current_user
|
||||
- if @snippet && current_user
|
||||
:javascript
|
||||
window.uploads_path = "#{upload_path('personal_snippet', @snippet)}";
|
||||
window.preview_markdown_path = "#{preview_markdown_snippet_path(@snippet)}";
|
||||
window.uploads_path = "#{upload_path('personal_snippet', id: @snippet.id)}";
|
||||
|
||||
= render template: "layouts/application"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
- project = local_assigns.fetch(:project)
|
||||
- issuable = local_assigns.fetch(:issuable)
|
||||
- model = local_assigns.fetch(:model)
|
||||
|
||||
- form = local_assigns.fetch(:form)
|
||||
- supports_slash_commands = issuable.new_record?
|
||||
- supports_slash_commands = !model.persisted?
|
||||
|
||||
- if supports_slash_commands
|
||||
- preview_url = preview_markdown_path(project, slash_commands_target_type: issuable.class.name)
|
||||
- preview_url = preview_markdown_path(project, slash_commands_target_type: model.class.name)
|
||||
- else
|
||||
- preview_url = preview_markdown_path(project)
|
||||
|
|
@ -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, project: project
|
||||
= render 'shared/form_elements/description', model: issuable, form: form, project: project
|
||||
|
||||
- if issuable.respond_to?(:confidential)
|
||||
.form-group
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
= page_specific_javascript_bundle_tag('snippet')
|
||||
|
||||
.snippet-form-holder
|
||||
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit" } do |f|
|
||||
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit common-note-form" } do |f|
|
||||
= form_errors(@snippet)
|
||||
|
||||
.form-group
|
||||
|
@ -11,6 +11,8 @@
|
|||
.col-sm-10
|
||||
= f.text_field :title, class: 'form-control', required: true, autofocus: true
|
||||
|
||||
= render 'shared/form_elements/description', model: @snippet, project: @project, form: f
|
||||
|
||||
= render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
|
||||
|
||||
.file-editor
|
||||
|
@ -23,6 +25,9 @@
|
|||
.file-content.code
|
||||
%pre#editor= @snippet.content
|
||||
= f.hidden_field :content, class: 'snippet-file-content'
|
||||
- if params[:files]
|
||||
- params[:files].each_with_index do |file, index|
|
||||
= hidden_field_tag "files[]", file, id: "files_#{index}"
|
||||
|
||||
.form-actions
|
||||
- if @snippet.new_record?
|
||||
|
|
|
@ -22,3 +22,10 @@
|
|||
|
||||
- if @snippet.updated_at != @snippet.created_at
|
||||
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
|
||||
%div
|
||||
- if @snippet.description.present?
|
||||
.description
|
||||
.wiki
|
||||
= markdown_field(@snippet, :description)
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @snippet.description
|
||||
|
|
4
changelogs/unreleased/12910-snippets-description.yml
Normal file
4
changelogs/unreleased/12910-snippets-description.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Support descriptions for snippets
|
||||
merge_request:
|
||||
author:
|
|
@ -2,6 +2,9 @@ resources :snippets, concerns: :awardable do
|
|||
member do
|
||||
get :raw
|
||||
post :mark_as_spam
|
||||
end
|
||||
|
||||
collection do
|
||||
post :preview_markdown
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ scope path: :uploads do
|
|||
constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ }
|
||||
|
||||
# create uploads for models, snippets (notes) available for now
|
||||
post ':model/:id/',
|
||||
post ':model',
|
||||
to: 'uploads#create',
|
||||
constraints: { model: /personal_snippet/, id: /\d+/ },
|
||||
as: 'upload'
|
||||
|
|
12
db/migrate/20170503114228_add_description_to_snippets.rb
Normal file
12
db/migrate/20170503114228_add_description_to_snippets.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class AddDescriptionToSnippets < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_column :snippets, :description, :text
|
||||
add_column :snippets, :description_html, :text
|
||||
end
|
||||
end
|
|
@ -1153,6 +1153,8 @@ ActiveRecord::Schema.define(version: 20170523091700) do
|
|||
t.text "title_html"
|
||||
t.text "content_html"
|
||||
t.integer "cached_markdown_version"
|
||||
t.text "description"
|
||||
t.text "description_html"
|
||||
end
|
||||
|
||||
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
|
||||
|
|
|
@ -43,6 +43,7 @@ Parameters:
|
|||
"id": 1,
|
||||
"title": "test",
|
||||
"file_name": "add.rb",
|
||||
"description": "Ruby test snippet",
|
||||
"author": {
|
||||
"id": 1,
|
||||
"username": "john_smith",
|
||||
|
@ -70,8 +71,9 @@ Parameters:
|
|||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `title` (required) - The title of a snippet
|
||||
- `file_name` (required) - The name of a snippet file
|
||||
- `description` (optional) - The description of a snippet
|
||||
- `code` (required) - The content of a snippet
|
||||
- `visibility` (required) - The snippet's visibility
|
||||
- `visibility` (optional) - The snippet's visibility
|
||||
|
||||
## Update snippet
|
||||
|
||||
|
@ -87,6 +89,7 @@ Parameters:
|
|||
- `snippet_id` (required) - The ID of a project's snippet
|
||||
- `title` (optional) - The title of a snippet
|
||||
- `file_name` (optional) - The name of a snippet file
|
||||
- `description` (optional) - The description of a snippet
|
||||
- `code` (optional) - The content of a snippet
|
||||
- `visibility` (optional) - The snippet's visibility
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ Example response:
|
|||
"id": 1,
|
||||
"title": "test",
|
||||
"file_name": "add.rb",
|
||||
"description": "Ruby test snippet",
|
||||
"author": {
|
||||
"id": 1,
|
||||
"username": "john_smith",
|
||||
|
@ -73,16 +74,17 @@ POST /snippets
|
|||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | String | yes | The title of a snippet |
|
||||
| `file_name` | String | yes | The name of a snippet file |
|
||||
| `content` | String | yes | The content of a snippet |
|
||||
| `visibility` | String | yes | The snippet's visibility |
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | String | yes | The title of a snippet |
|
||||
| `file_name` | String | yes | The name of a snippet file |
|
||||
| `content` | String | yes | The content of a snippet |
|
||||
| `description` | String | no | The description of a snippet |
|
||||
| `visibility` | String | no | The snippet's visibility |
|
||||
|
||||
|
||||
``` bash
|
||||
curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "file_name": "test.txt", "visibility": "internal" }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets
|
||||
curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
@ -92,6 +94,7 @@ Example response:
|
|||
"id": 1,
|
||||
"title": "This is a snippet",
|
||||
"file_name": "test.txt",
|
||||
"description": "Hello World snippet",
|
||||
"author": {
|
||||
"id": 1,
|
||||
"username": "john_smith",
|
||||
|
@ -117,13 +120,14 @@ PUT /snippets/:id
|
|||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | Integer | yes | The ID of a snippet |
|
||||
| `title` | String | no | The title of a snippet |
|
||||
| `file_name` | String | no | The name of a snippet file |
|
||||
| `content` | String | no | The content of a snippet |
|
||||
| `visibility` | String | no | The snippet's visibility |
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | Integer | yes | The ID of a snippet |
|
||||
| `title` | String | no | The title of a snippet |
|
||||
| `file_name` | String | no | The name of a snippet file |
|
||||
| `description` | String | no | The description of a snippet |
|
||||
| `content` | String | no | The content of a snippet |
|
||||
| `visibility` | String | no | The snippet's visibility |
|
||||
|
||||
|
||||
``` bash
|
||||
|
@ -137,6 +141,7 @@ Example response:
|
|||
"id": 1,
|
||||
"title": "test",
|
||||
"file_name": "add.rb",
|
||||
"description": "description of snippet",
|
||||
"author": {
|
||||
"id": 1,
|
||||
"username": "john_smith",
|
||||
|
|
|
@ -224,7 +224,7 @@ module API
|
|||
end
|
||||
|
||||
class ProjectSnippet < Grape::Entity
|
||||
expose :id, :title, :file_name
|
||||
expose :id, :title, :file_name, :description
|
||||
expose :author, using: Entities::UserBasic
|
||||
expose :updated_at, :created_at
|
||||
|
||||
|
@ -234,7 +234,7 @@ module API
|
|||
end
|
||||
|
||||
class PersonalSnippet < Grape::Entity
|
||||
expose :id, :title, :file_name
|
||||
expose :id, :title, :file_name, :description
|
||||
expose :author, using: Entities::UserBasic
|
||||
expose :updated_at, :created_at
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ module API
|
|||
requires :title, type: String, desc: 'The title of the snippet'
|
||||
requires :file_name, type: String, desc: 'The file name of the snippet'
|
||||
requires :code, type: String, desc: 'The content of the snippet'
|
||||
optional :description, type: String, desc: 'The description of a snippet'
|
||||
requires :visibility, type: String,
|
||||
values: Gitlab::VisibilityLevel.string_values,
|
||||
desc: 'The visibility of the snippet'
|
||||
|
@ -77,6 +78,7 @@ module API
|
|||
optional :title, type: String, desc: 'The title of the snippet'
|
||||
optional :file_name, type: String, desc: 'The file name of the snippet'
|
||||
optional :code, type: String, desc: 'The content of the snippet'
|
||||
optional :description, type: String, desc: 'The description of a snippet'
|
||||
optional :visibility, type: String,
|
||||
values: Gitlab::VisibilityLevel.string_values,
|
||||
desc: 'The visibility of the snippet'
|
||||
|
|
|
@ -58,6 +58,7 @@ module API
|
|||
requires :title, type: String, desc: 'The title of a snippet'
|
||||
requires :file_name, type: String, desc: 'The name of a snippet file'
|
||||
requires :content, type: String, desc: 'The content of a snippet'
|
||||
optional :description, type: String, desc: 'The description of a snippet'
|
||||
optional :visibility, type: String,
|
||||
values: Gitlab::VisibilityLevel.string_values,
|
||||
default: 'internal',
|
||||
|
@ -85,6 +86,7 @@ module API
|
|||
optional :title, type: String, desc: 'The title of a snippet'
|
||||
optional :file_name, type: String, desc: 'The name of a snippet file'
|
||||
optional :content, type: String, desc: 'The content of a snippet'
|
||||
optional :description, type: String, desc: 'The description of a snippet'
|
||||
optional :visibility, type: String,
|
||||
values: Gitlab::VisibilityLevel.string_values,
|
||||
desc: 'The visibility of the snippet'
|
||||
|
|
|
@ -78,8 +78,18 @@ describe Projects::SnippetsController do
|
|||
post :create, {
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project,
|
||||
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
|
||||
project_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
|
||||
}.merge(additional_params)
|
||||
|
||||
Snippet.last
|
||||
end
|
||||
|
||||
it 'creates the snippet correctly' do
|
||||
snippet = create_snippet(project, visibility_level: Snippet::PRIVATE)
|
||||
|
||||
expect(snippet.title).to eq('Title')
|
||||
expect(snippet.content).to eq('Content')
|
||||
expect(snippet.description).to eq('Description')
|
||||
end
|
||||
|
||||
context 'when the snippet is spam' do
|
||||
|
|
|
@ -171,12 +171,50 @@ describe SnippetsController do
|
|||
sign_in(user)
|
||||
|
||||
post :create, {
|
||||
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
|
||||
personal_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
|
||||
}.merge(additional_params)
|
||||
|
||||
Snippet.last
|
||||
end
|
||||
|
||||
it 'creates the snippet correctly' do
|
||||
snippet = create_snippet(visibility_level: Snippet::PRIVATE)
|
||||
|
||||
expect(snippet.title).to eq('Title')
|
||||
expect(snippet.content).to eq('Content')
|
||||
expect(snippet.description).to eq('Description')
|
||||
end
|
||||
|
||||
context 'when the snippet description contains a file' do
|
||||
let(:picture_file) { '/temp/secret56/picture.jpg' }
|
||||
let(:text_file) { '/temp/secret78/text.txt' }
|
||||
let(:description) do
|
||||
"Description with picture: ![picture](/uploads#{picture_file}) and "\
|
||||
"text: [text.txt](/uploads#{text_file})"
|
||||
end
|
||||
|
||||
before do
|
||||
allow(FileUtils).to receive(:mkdir_p)
|
||||
allow(FileUtils).to receive(:move)
|
||||
end
|
||||
|
||||
subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
|
||||
|
||||
it 'creates the snippet' do
|
||||
expect { subject }.to change { Snippet.count }.by(1)
|
||||
end
|
||||
|
||||
it 'stores the snippet description correctly' do
|
||||
snippet = subject
|
||||
|
||||
expected_description = "Description with picture: "\
|
||||
"![picture](/uploads/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\
|
||||
"text: [text.txt](/uploads/personal_snippet/#{snippet.id}/secret78/text.txt)"
|
||||
|
||||
expect(snippet.description).to eq(expected_description)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the snippet is spam' do
|
||||
before do
|
||||
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
|
||||
|
|
|
@ -3,6 +3,7 @@ FactoryGirl.define do
|
|||
author
|
||||
title { generate(:title) }
|
||||
content { generate(:title) }
|
||||
description { generate(:title) }
|
||||
file_name { generate(:filename) }
|
||||
|
||||
trait :public do
|
||||
|
|
86
spec/features/projects/snippets/create_snippet_spec.rb
Normal file
86
spec/features/projects/snippets/create_snippet_spec.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'Create Snippet', :js, feature: true do
|
||||
include DropzoneHelper
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository, :public) }
|
||||
|
||||
def fill_form
|
||||
fill_in 'project_snippet_title', with: 'My Snippet Title'
|
||||
fill_in 'project_snippet_description', with: 'My Snippet **Description**'
|
||||
page.within('.file-editor') do
|
||||
find('.ace_editor').native.send_keys('Hello World!')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user is authenticated' do
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
login_as(user)
|
||||
|
||||
visit namespace_project_snippets_path(project.namespace, project)
|
||||
|
||||
click_on('New snippet')
|
||||
end
|
||||
|
||||
it 'creates a new snippet' do
|
||||
fill_form
|
||||
click_button('Create snippet')
|
||||
wait_for_ajax
|
||||
|
||||
expect(page).to have_content('My Snippet Title')
|
||||
expect(page).to have_content('Hello World!')
|
||||
page.within('.snippet-header .description') do
|
||||
expect(page).to have_content('My Snippet Description')
|
||||
expect(page).to have_selector('strong')
|
||||
end
|
||||
end
|
||||
|
||||
it 'uploads a file when dragging into textarea' do
|
||||
fill_form
|
||||
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
|
||||
expect(page.find_field("project_snippet_description").value).to have_content('banana_sample')
|
||||
|
||||
click_button('Create snippet')
|
||||
wait_for_ajax
|
||||
|
||||
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
|
||||
expect(link).to match(%r{/#{Regexp.escape(project.full_path) }/uploads/\h{32}/banana_sample\.gif\z})
|
||||
end
|
||||
|
||||
it 'creates a snippet when all reuiqred fields are filled in after validation failing' do
|
||||
fill_in 'project_snippet_title', with: 'My Snippet Title'
|
||||
click_button('Create snippet')
|
||||
|
||||
expect(page).to have_selector('#error_explanation')
|
||||
|
||||
fill_form
|
||||
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
|
||||
click_button('Create snippet')
|
||||
wait_for_ajax
|
||||
|
||||
expect(page).to have_content('My Snippet Title')
|
||||
expect(page).to have_content('Hello World!')
|
||||
page.within('.snippet-header .description') do
|
||||
expect(page).to have_content('My Snippet Description')
|
||||
expect(page).to have_selector('strong')
|
||||
end
|
||||
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
|
||||
expect(link).to match(%r{/#{Regexp.escape(project.full_path) }/uploads/\h{32}/banana_sample\.gif\z})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user is not authenticated' do
|
||||
it 'shows a public snippet on the index page but not the New snippet button' do
|
||||
snippet = create(:project_snippet, :public, project: project)
|
||||
|
||||
visit namespace_project_snippets_path(project.namespace, project)
|
||||
|
||||
expect(page).to have_content(snippet.title)
|
||||
expect(page).not_to have_content('New snippet')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,24 +1,71 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'Create Snippet', :js, feature: true do
|
||||
include DropzoneHelper
|
||||
|
||||
before do
|
||||
login_as :user
|
||||
visit new_snippet_path
|
||||
end
|
||||
|
||||
scenario 'Authenticated user creates a snippet' do
|
||||
def fill_form
|
||||
fill_in 'personal_snippet_title', with: 'My Snippet Title'
|
||||
fill_in 'personal_snippet_description', with: 'My Snippet **Description**'
|
||||
page.within('.file-editor') do
|
||||
find('.ace_editor').native.send_keys 'Hello World!'
|
||||
end
|
||||
end
|
||||
|
||||
click_button 'Create snippet'
|
||||
scenario 'Authenticated user creates a snippet' do
|
||||
fill_form
|
||||
|
||||
click_button('Create snippet')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('My Snippet Title')
|
||||
page.within('.snippet-header .description') do
|
||||
expect(page).to have_content('My Snippet Description')
|
||||
expect(page).to have_selector('strong')
|
||||
end
|
||||
expect(page).to have_content('Hello World!')
|
||||
end
|
||||
|
||||
scenario 'uploads a file when dragging into textarea' do
|
||||
fill_form
|
||||
|
||||
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
|
||||
expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
|
||||
|
||||
click_button('Create snippet')
|
||||
wait_for_requests
|
||||
|
||||
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
|
||||
expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
|
||||
end
|
||||
|
||||
scenario 'validation fails for the first time' do
|
||||
fill_in 'personal_snippet_title', with: 'My Snippet Title'
|
||||
click_button('Create snippet')
|
||||
|
||||
expect(page).to have_selector('#error_explanation')
|
||||
|
||||
fill_form
|
||||
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
|
||||
click_button('Create snippet')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('My Snippet Title')
|
||||
page.within('.snippet-header .description') do
|
||||
expect(page).to have_content('My Snippet Description')
|
||||
expect(page).to have_selector('strong')
|
||||
end
|
||||
expect(page).to have_content('Hello World!')
|
||||
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
|
||||
expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
|
||||
end
|
||||
|
||||
scenario 'Authenticated user creates a snippet with + in filename' do
|
||||
fill_in 'personal_snippet_title', with: 'My Snippet Title'
|
||||
page.within('.file-editor') do
|
||||
|
|
38
spec/features/snippets/edit_snippet_spec.rb
Normal file
38
spec/features/snippets/edit_snippet_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'Edit Snippet', :js, feature: true do
|
||||
include DropzoneHelper
|
||||
|
||||
let(:file_name) { 'test.rb' }
|
||||
let(:content) { 'puts "test"' }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
|
||||
|
||||
before do
|
||||
login_as(user)
|
||||
|
||||
visit edit_snippet_path(snippet)
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'updates the snippet' do
|
||||
fill_in 'personal_snippet_title', with: 'New Snippet Title'
|
||||
|
||||
click_button('Save changes')
|
||||
wait_for_ajax
|
||||
|
||||
expect(page).to have_content('New Snippet Title')
|
||||
end
|
||||
|
||||
it 'updates the snippet with files attached' do
|
||||
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
|
||||
|
||||
click_button('Save changes')
|
||||
wait_for_ajax
|
||||
|
||||
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
|
||||
expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
|
||||
end
|
||||
end
|
|
@ -92,6 +92,7 @@ Milestone:
|
|||
ProjectSnippet:
|
||||
- id
|
||||
- title
|
||||
- description
|
||||
- content
|
||||
- author_id
|
||||
- project_id
|
||||
|
|
|
@ -36,11 +36,34 @@ describe API::ProjectSnippets do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:project_id/snippets/:id' do
|
||||
let(:user) { create(:user) }
|
||||
let(:snippet) { create(:project_snippet, :public, project: project) }
|
||||
|
||||
it 'returns snippet json' do
|
||||
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(json_response['title']).to eq(snippet.title)
|
||||
expect(json_response['description']).to eq(snippet.description)
|
||||
expect(json_response['file_name']).to eq(snippet.file_name)
|
||||
end
|
||||
|
||||
it 'returns 404 for invalid snippet id' do
|
||||
get api("/projects/#{project.id}/snippets/1234", user)
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
expect(json_response['message']).to eq('404 Not found')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:project_id/snippets/' do
|
||||
let(:params) do
|
||||
{
|
||||
title: 'Test Title',
|
||||
file_name: 'test.rb',
|
||||
description: 'test description',
|
||||
code: 'puts "hello world"',
|
||||
visibility: 'public'
|
||||
}
|
||||
|
@ -52,6 +75,7 @@ describe API::ProjectSnippets do
|
|||
expect(response).to have_http_status(201)
|
||||
snippet = ProjectSnippet.find(json_response['id'])
|
||||
expect(snippet.content).to eq(params[:code])
|
||||
expect(snippet.description).to eq(params[:description])
|
||||
expect(snippet.title).to eq(params[:title])
|
||||
expect(snippet.file_name).to eq(params[:file_name])
|
||||
expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
|
||||
|
@ -106,12 +130,14 @@ describe API::ProjectSnippets do
|
|||
|
||||
it 'updates snippet' do
|
||||
new_content = 'New content'
|
||||
new_description = 'New description'
|
||||
|
||||
put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
|
||||
put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content, description: new_description
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
snippet.reload
|
||||
expect(snippet.content).to eq(new_content)
|
||||
expect(snippet.description).to eq(new_description)
|
||||
end
|
||||
|
||||
it 'returns 404 for invalid snippet id' do
|
||||
|
|
|
@ -80,11 +80,33 @@ describe API::Snippets do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /snippets/:id' do
|
||||
let(:snippet) { create(:personal_snippet, author: user) }
|
||||
|
||||
it 'returns snippet json' do
|
||||
get api("/snippets/#{snippet.id}", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(json_response['title']).to eq(snippet.title)
|
||||
expect(json_response['description']).to eq(snippet.description)
|
||||
expect(json_response['file_name']).to eq(snippet.file_name)
|
||||
end
|
||||
|
||||
it 'returns 404 for invalid snippet id' do
|
||||
get api("/snippets/1234", user)
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
expect(json_response['message']).to eq('404 Not found')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /snippets/' do
|
||||
let(:params) do
|
||||
{
|
||||
title: 'Test Title',
|
||||
file_name: 'test.rb',
|
||||
description: 'test description',
|
||||
content: 'puts "hello world"',
|
||||
visibility: 'public'
|
||||
}
|
||||
|
@ -97,6 +119,7 @@ describe API::Snippets do
|
|||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(json_response['title']).to eq(params[:title])
|
||||
expect(json_response['description']).to eq(params[:description])
|
||||
expect(json_response['file_name']).to eq(params[:file_name])
|
||||
end
|
||||
|
||||
|
@ -150,12 +173,14 @@ describe API::Snippets do
|
|||
|
||||
it 'updates snippet' do
|
||||
new_content = 'New content'
|
||||
new_description = 'New description'
|
||||
|
||||
put api("/snippets/#{snippet.id}", user), content: new_content
|
||||
put api("/snippets/#{snippet.id}", user), content: new_content, description: new_description
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
snippet.reload
|
||||
expect(snippet.content).to eq(new_content)
|
||||
expect(snippet.description).to eq(new_description)
|
||||
end
|
||||
|
||||
it 'returns 404 for invalid snippet id' do
|
||||
|
|
25
spec/uploaders/file_mover_spec.rb
Normal file
25
spec/uploaders/file_mover_spec.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe FileMover do
|
||||
let(:filename) { 'banana_sample.gif' }
|
||||
let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) }
|
||||
let(:temp_description) { 'test ![banana_sample](/uploads/temp/secret55/banana_sample.gif)' }
|
||||
let(:temp_file_path) { File.join('secret55', filename).to_s }
|
||||
let(:file_path) { File.join('uploads', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
|
||||
|
||||
let(:snippet) { create(:personal_snippet, description: temp_description) }
|
||||
|
||||
subject { described_class.new(file_path, snippet).execute }
|
||||
|
||||
describe '#execute' do
|
||||
it 'updates the description correctly' do
|
||||
expect(FileUtils).to receive(:mkdir_p).with(a_string_including(file_path))
|
||||
expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path))
|
||||
|
||||
subject
|
||||
|
||||
expect(snippet.reload.description)
|
||||
.to eq("test ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue