Add name to shared links (#910)

* Add name to shared links

* Add changelog entry
This commit is contained in:
Uku Taht 2021-04-06 14:32:38 +03:00 committed by GitHub
parent 968d280304
commit 58cff47b6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 140 additions and 19 deletions

View File

@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- Site switching keybinds (1-9 for respective sites) plausible/analytics#735
- Glob (wildcard) based pageview goals plausible/analytics#750
- Support for embedding shared links in an iframe plausible/analytics#812
- Add name/label to shared links plausible/analytics#910
### Fixed
- Capitalized date/time selection keybinds not working plausible/analytics#709

View File

@ -4,6 +4,7 @@ defmodule Plausible.Site.SharedLink do
schema "shared_links" do
belongs_to :site, Plausible.Site
field :name, :string
field :slug, :string
field :password_hash, :string
field :password, :string, virtual: true
@ -13,8 +14,8 @@ defmodule Plausible.Site.SharedLink do
def changeset(link, attrs \\ %{}) do
link
|> cast(attrs, [:slug, :password])
|> validate_required([:slug])
|> cast(attrs, [:slug, :password, :name])
|> validate_required([:slug, :name])
|> unique_constraint(:slug)
|> hash_password()
end

View File

@ -541,6 +541,40 @@ defmodule PlausibleWeb.SiteController do
end
end
def edit_shared_link(conn, %{"website" => website, "slug" => slug}) do
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
shared_link = Repo.get_by(Plausible.Site.SharedLink, slug: slug)
changeset = Plausible.Site.SharedLink.changeset(shared_link, %{})
conn
|> assign(:skip_plausible_tracking, true)
|> render("edit_shared_link.html",
site: site,
changeset: changeset,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
def update_shared_link(conn, %{"website" => website, "slug" => slug, "shared_link" => params}) do
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
shared_link = Repo.get_by(Plausible.Site.SharedLink, slug: slug)
changeset = Plausible.Site.SharedLink.changeset(shared_link, params)
case Repo.update(changeset) do
{:ok, _created} ->
redirect(conn, to: "/#{URI.encode_www_form(site.domain)}/settings/visibility")
{:error, changeset} ->
conn
|> assign(:skip_plausible_tracking, true)
|> render("edit_shared_link.html",
site: site,
changeset: changeset,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
end
def delete_shared_link(conn, %{"website" => website, "slug" => slug}) do
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)

View File

@ -179,6 +179,8 @@ defmodule PlausibleWeb.Router do
get "/sites/:website/shared-links/new", SiteController, :new_shared_link
post "/sites/:website/shared-links", SiteController, :create_shared_link
get "/sites/:website/shared-links/:slug/edit", SiteController, :edit_shared_link
put "/sites/:website/shared-links/:slug", SiteController, :update_shared_link
delete "/sites/:website/shared-links/:slug", SiteController, :delete_shared_link
get "/sites/:website/custom-domains/new", SiteController, :new_custom_domain

View File

@ -0,0 +1,12 @@
<%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/shared-links/#{@changeset.data.slug}", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black dark:text-gray-100">Edit shared link</h2>
<div class="my-4">
<%= label f, :name, "Name", class: "block text-sm font-medium text-gray-700 dark:text-gray-100" %>
<div class="mt-1">
<%= text_input f, :name, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md", required: "required" %>
<%= error_tag f, :name %>
</div>
</div>
<%= submit "Edit shared link", class: "button mt-4 w-full" %>
<% end %>

View File

@ -1,13 +1,23 @@
<%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/shared-links", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black dark:text-gray-100">New shared link</h2>
<div class="my-4 dark:text-gray-100">
Add a password or leave it blank so anyone with the link can see the stats.
Once the link is created, we cannot reveal the password. Please make sure you save it in a secure place.
</div>
<div class="my-6">
<%= label f, :password, "Password (optional)", class: "block text-sm font-bold dark:text-gray-100" %>
<%= password_input f, :password, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
<%= error_tag f, :password %>
<div class="my-4">
<%= label f, :name, "Name", class: "block text-sm font-medium text-gray-700 dark:text-gray-100" %>
<div class="mt-1">
<%= text_input f, :name, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md", required: "required" %>
<%= error_tag f, :name %>
</div>
</div>
<div class="my-4">
<%= label f, :password, "Password (optional)", class: "block text-sm font-medium text-gray-700 dark:text-gray-100" %>
<div class="mt-1">
<%= password_input f, :password, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %>
<%= error_tag f, :password %>
<p class="mt-2 text-sm text-gray-500">
Password protection is optional. Please make sure you save it in a secure place. Once the link is created, we cannot reveal the password.
</p>
</div>
</div>
<%= submit "Create shared link", class: "button mt-4 w-full" %>

View File

@ -33,16 +33,30 @@
<% end %>
</header>
<div class="mt-6">
<div class="mt-6 flex flex-col divide-y divide-gray-200">
<%= for link <- @shared_links do %>
<div class="relative flex w-full max-w-xl mt-2 text-sm">
<input type="text" id="<%= link.slug %>" readonly="readonly" value="<%= shared_link_dest(@site, link) %>" class="w-full p-2 text-gray-700 bg-gray-100 border border-transparent rounded rounded-r-none outline-none appearance-none transition dark:bg-gray-900 dark:text-gray-300 focus:outline-none focus:border-gray-300 dark:focus:border-gray-500" />
<button onclick="var input = document.getElementById('<%= link.slug %>'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="px-4 py-2 text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825">
<svg class="feather-sm" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/shared-links/#{link.slug}", method: :delete, class: "py-2 px-4 bg-gray-200 dark:bg-gray-850 text-red-600 dark:text-red-500 rounded-l-none hover:bg-gray-300 dark:hover:bg-gray-825", data: [confirm: "Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."]) do %>
<svg class="feather feather-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
<% end %>
<div class="py-4">
<label for="<%= link.slug %>" class="flex content-center text-sm font-medium text-gray-700">
<%= link.name %>
<%= if link.password_hash do %>
<svg class="ml-1 w-4 h-4 mt-px" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
<% else %>
<svg class="ml-1 w-4 h-4 mt-px" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"></path></svg>
<% end %>
</label>
<div class="relative flex w-full mt-2 text-sm">
<input type="text" id="<%= link.slug %>" readonly="readonly" value="<%= shared_link_dest(@site, link) %>" class="w-full p-2 text-gray-700 bg-gray-100 border-none rounded rounded-r-none outline-none appearance-none transition dark:bg-gray-900 dark:text-gray-300 focus:outline-none focus:border-gray-300 dark:focus:border-gray-500" />
<button onclick="var input = document.getElementById('<%= link.slug %>'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="px-4 py-2 inline-flex items-center text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"></path><path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"></path></svg>
<span class="ml-1">Copy</span>
</button>
<%= link(to: "/sites/#{URI.encode_www_form(@site.domain)}/shared-links/#{link.slug}/edit", class: "px-4 py-2 inline-flex items-center text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825") do %>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path><path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"></path></svg>
<% end %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/shared-links/#{link.slug}", method: :delete, class: "py-2 px-4 inline-flex items-center bg-gray-200 dark:bg-gray-850 text-red-600 dark:text-red-500 rounded-l-none hover:bg-gray-300 dark:hover:bg-gray-825", data: [confirm: "Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."]) do %>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
<% end %>
</div>
</div>
<% end %>

View File

@ -0,0 +1,15 @@
defmodule Plausible.Repo.Migrations.AddNameToSharedLinks do
use Ecto.Migration
def change do
alter table(:shared_links) do
add :name, :string
end
execute "UPDATE shared_links SET name=slug"
alter table(:shared_links) do
modify :name, :string, null: false
end
end
end

View File

@ -493,23 +493,54 @@ defmodule PlausibleWeb.SiteControllerTest do
setup [:create_user, :log_in, :create_site]
test "creates shared link without password", %{conn: conn, site: site} do
post(conn, "/sites/#{site.domain}/shared-links", %{"shared_link" => %{}})
post(conn, "/sites/#{site.domain}/shared-links", %{
"shared_link" => %{"name" => "Link name"}
})
link = Repo.one(Plausible.Site.SharedLink)
refute is_nil(link.slug)
assert is_nil(link.password_hash)
assert link.name == "Link name"
end
test "creates shared link with password", %{conn: conn, site: site} do
post(conn, "/sites/#{site.domain}/shared-links", %{
"shared_link" => %{"password" => "password"}
"shared_link" => %{"password" => "password", "name" => "New name"}
})
link = Repo.one(Plausible.Site.SharedLink)
refute is_nil(link.slug)
refute is_nil(link.password_hash)
assert link.name == "New name"
end
end
describe "GET /sites/:website/shared-links/edit" do
setup [:create_user, :log_in, :create_site]
test "shows form to edit shared link", %{conn: conn, site: site} do
link = insert(:shared_link, site: site)
conn = get(conn, "/sites/#{site.domain}/shared-links/#{link.slug}/edit")
assert html_response(conn, 200) =~ "Edit shared link"
end
end
describe "PUT /sites/:website/shared-links/:slug" do
setup [:create_user, :log_in, :create_site]
test "can update link name", %{conn: conn, site: site} do
link = insert(:shared_link, site: site)
put(conn, "/sites/#{site.domain}/shared-links/#{link.slug}", %{
"shared_link" => %{"name" => "Updated link name"}
})
link = Repo.one(Plausible.Site.SharedLink)
assert link.name == "Updated link name"
end
end

View File

@ -147,6 +147,7 @@ defmodule Plausible.Factory do
def shared_link_factory do
%Plausible.Site.SharedLink{
name: "Link name",
slug: Nanoid.generate()
}
end