Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add name to shared links #910

Merged
merged 2 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lib/plausible/site/shared_link.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
34 changes: 34 additions & 0 deletions lib/plausible_web/controllers/site_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions lib/plausible_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions lib/plausible_web/templates/site/edit_shared_link.html.eex
Original file line number Diff line number Diff line change
@@ -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 %>
22 changes: 16 additions & 6 deletions lib/plausible_web/templates/site/new_shared_link.html.eex
Original file line number Diff line number Diff line change
@@ -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" %>
Expand Down
32 changes: 23 additions & 9 deletions lib/plausible_web/templates/site/settings_visibility.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -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 %>

Expand Down
15 changes: 15 additions & 0 deletions priv/repo/migrations/20210406073254_add_name_to_shared_links.exs
Original file line number Diff line number Diff line change
@@ -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
35 changes: 33 additions & 2 deletions test/plausible_web/controllers/site_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ defmodule Plausible.Factory do

def shared_link_factory do
%Plausible.Site.SharedLink{
name: "Link name",
slug: Nanoid.generate()
}
end
Expand Down