-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Slug editor in the dashboard page for a video
Adds a modal that allows to change or reset the slug of a video, conveniently and without having to access the production server console.
- Loading branch information
Showing
13 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Modal } from "bootstrap"; | ||
const slugModal = document.querySelector("#slugModal"); | ||
|
||
if (slugModal) { | ||
const titlePlaceholder = slugModal.querySelector("[data-target-title]"); | ||
const idPlaceholder = slugModal.querySelector("[data-target-id]"); | ||
const slugPlaceholder = slugModal.querySelector("[data-target-slug]"); | ||
const resetCheckbox = slugModal.querySelector("#modal_slug_reset"); | ||
|
||
resetCheckbox.addEventListener("change", () => { | ||
if (resetCheckbox.checked) { | ||
slugPlaceholder.setAttribute("disabled", "disabled"); | ||
} else { | ||
slugPlaceholder.removeAttribute("disabled"); | ||
} | ||
}); | ||
|
||
slugModal.addEventListener("shown.bs.modal", () => { | ||
slugPlaceholder.select(); | ||
}); | ||
|
||
function installSlugModalOpen(node) { | ||
node.addEventListener("click", () => { | ||
const id = node.getAttribute("data-id"); | ||
const title = node.getAttribute("data-title"); | ||
const slug = node.getAttribute("data-slug"); | ||
console.log(`Opening modal for ${id} x ${slug}`); | ||
|
||
titlePlaceholder.innerText = title; | ||
idPlaceholder.value = id; | ||
slugPlaceholder.value = slug; | ||
|
||
const modal = new Modal(slugModal); | ||
modal.show(); | ||
}); | ||
} | ||
|
||
const slugModalOpen = document.querySelector("#slugModalOpen"); | ||
|
||
if (slugModalOpen) { | ||
installSlugModalOpen(slugModalOpen); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
class ModalSlug | ||
include ActiveModel::API | ||
include ActiveModel::Attributes | ||
|
||
attribute :id, :integer | ||
attribute :slug, :string | ||
attribute :reset, :boolean, default: false | ||
validates :id, :slug, presence: true | ||
|
||
def update_slug! | ||
next_slug = if reset | ||
nil | ||
else | ||
slug | ||
end | ||
video.update(slug: next_slug) | ||
end | ||
|
||
def video | ||
@video ||= Video.find(id) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
.modal.fade#slugModal{tabindex: -1, aria: { label: 'slugModalLabel', hidden: true } } | ||
.modal-dialog | ||
.modal-content | ||
.modal-header | ||
%h5.modal-title#slugModalLabel Actualizar identificador | ||
%button.btn-close{type: 'button', data: { 'bs-dismiss' => 'modal' }, aria: { label: 'Cerrar' }} | ||
= simple_form_for ModalSlug.new, url: update_slug_dashboard_videos_path, method: 'put' do |form| | ||
= form.input :id, as: :hidden, input_html: { data: { 'target-id': '' } } | ||
.modal-body | ||
Actualizar el identificador de <strong data-target-title>este vídeo</strong>. | ||
%fieldset.mt-3 | ||
= form.input :slug, input_html: {data: { 'target-slug': "" }} | ||
= form.input :reset, as: :boolean, wrapper_html: {class: 'mt-3' } | ||
.modal-footer | ||
%button.btn.btn-secondary{type: 'button', data: { 'bs-dismiss' => 'modal' }} Cancelar | ||
= form.submit 'Actualizar', class: 'btn btn-primary' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
|
||
RSpec.describe 'Dashboard video', :js do | ||
let(:user) { create(:user) } | ||
let(:video) { create(:video) } | ||
|
||
before do | ||
Capybara.app_host = 'http://dashboard.lvh.me:9080' | ||
Capybara.server_port = 9080 | ||
login_as user, scope: :user | ||
end | ||
|
||
after do | ||
Capybara.app_host = nil | ||
Capybara.server_port = nil | ||
end | ||
|
||
it 'can update the slug of a video' do | ||
video.update(title: 'An updated video', slug: 'original-slug') | ||
|
||
visit dashboard_playlist_video_path(video.slug, playlist_id: video.playlist.slug) | ||
find_by_id('slugModalOpen').click | ||
|
||
within '#slugModal' do | ||
fill_in 'Identificador', with: 'my-custom-slug' | ||
click_on 'Actualizar' | ||
end | ||
|
||
aggregate_failures do | ||
expect(video.reload.slug).to eq 'my-custom-slug' | ||
expect(page).to have_text 'Identificador del vídeo actualizado correctamente.' | ||
end | ||
end | ||
|
||
it 'can reset the slug of a video' do | ||
video.update(title: 'An updated video', slug: 'original-slug') | ||
|
||
visit dashboard_playlist_video_path(video.slug, playlist_id: video.playlist.slug) | ||
find_by_id('slugModalOpen').click | ||
|
||
within '#slugModal' do | ||
check 'Reestablecer al valor por defecto' | ||
click_on 'Actualizar' | ||
end | ||
|
||
aggregate_failures do | ||
expect(page).to have_text 'Identificador del vídeo actualizado correctamente.' | ||
expect(video.reload.slug).to eq 'an-updated-video' | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
|
||
RSpec.describe ModalSlug do | ||
let(:video) { create(:video, title: 'My video', slug: 'old-slug') } | ||
let(:instance) { described_class.new(id: video.id) } | ||
|
||
describe 'when changing the slug of a video' do | ||
it 'supports a custom slug' do | ||
instance.slug = 'another-slug' | ||
instance.update_slug! | ||
expect(video.reload.slug).to eq 'another-slug' | ||
end | ||
|
||
it 'supports a pre-generated slug' do | ||
instance.reset = true | ||
instance.update_slug! | ||
expect(video.reload.slug).to eq 'my-video' | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
|
||
RSpec.describe 'Update slug' do | ||
let(:video) do | ||
create(:video, title: 'Original slug').tap do |v| | ||
v.update(slug: 'old-slug') | ||
end | ||
end | ||
|
||
before do | ||
host! 'dashboard.lvh.me' | ||
end | ||
|
||
it 'passes the slug smoke test' do | ||
expect(video.slug).to eq 'old-slug' | ||
end | ||
|
||
describe 'without a login' do | ||
it 'redirects to login' do | ||
put '/videos/modal/slug', params: { modal_slug: { id: video.id, slug: 'new-slug' } } | ||
expect(response).to redirect_to '/users/sign_in' | ||
end | ||
|
||
it 'does not change the video' do | ||
put '/videos/modal/slug', params: { modal_slug: { id: video.id, slug: 'new-slug' } } | ||
video.reload | ||
expect(video).to have_attributes(slug: 'old-slug') | ||
end | ||
end | ||
|
||
describe 'with a login' do | ||
let(:user) { create(:user) } | ||
|
||
before do | ||
sign_in user, scope: :user | ||
end | ||
|
||
describe 'when the request is valid for changing attributes' do | ||
it 'redirects to the video page' do | ||
put '/videos/modal/slug', params: { modal_slug: { id: video.id, slug: 'new-slug' } } | ||
video.reload | ||
expect(response).to redirect_to "/playlists/#{video.playlist.slug}/videos/#{video.slug}" | ||
end | ||
|
||
it 'updates the video slug' do | ||
put '/videos/modal/slug', params: { modal_slug: { id: video.id, slug: 'new-slug' } } | ||
video.reload | ||
expect(video).to have_attributes(slug: 'new-slug') | ||
end | ||
end | ||
|
||
describe 'when the request is valid for resetting attributes' do | ||
it 'redirects to the video page' do | ||
put '/videos/modal/slug', params: { modal_slug: { id: video.id, reset: '1' } } | ||
video.reload | ||
expect(response).to redirect_to "/playlists/#{video.playlist.slug}/videos/#{video.slug}" | ||
end | ||
|
||
it 'updates the video slug' do | ||
put '/videos/modal/slug', params: { modal_slug: { id: video.id, reset: '1' } } | ||
video.reload | ||
expect(video).to have_attributes(slug: 'original-slug') | ||
end | ||
end | ||
end | ||
end |