Skip to content

Commit

Permalink
Reimplement user search in enterprise plans CRM and fix plan prefill (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
zoldar authored Dec 17, 2024
1 parent 2848ce8 commit 3e4d59d
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 23 deletions.
1 change: 0 additions & 1 deletion lib/plausible/billing/enterprise_plan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,5 @@ defmodule Plausible.Billing.EnterprisePlan do
model
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> unique_constraint(:user_id)
end
end
107 changes: 85 additions & 22 deletions lib/plausible/crm_extensions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,71 @@ defmodule Plausible.CrmExtensions do
]
end

def javascripts(%{assigns: %{context: context}})
when context in ["sites", "billing"] do
def javascripts(%{assigns: %{context: "billing", resource: "enterprise_plan", changeset: %{}}}) do
[
Phoenix.HTML.raw("""
<script type="text/javascript">
(() => {
const publicField = document.querySelector("#kaffy-search-field")
const searchForm = document.querySelector("#kaffy-filters-form")
const searchField = document.querySelector("#kaffy-filter-search")
(async () => {
const CHECK_INTERVAL = 300
const userIdField = document.querySelector("#enterprise_plan_user_id")
const userIdLabel = document.querySelector("label[for=enterprise_plan_user_id]")
const dataList = document.createElement("datalist")
dataList.id = "user-choices"
userIdField.after(dataList)
userIdField.setAttribute("list", "user-choices")
userIdField.setAttribute("type", "text")
const labelSpan = document.createElement("span")
userIdLabel.appendChild(labelSpan)
let updateAction;
const updateLabel = async (id) => {
id = Number(id)
if (!isNaN(id) && id > 0) {
const response = await fetch(`/crm/billing/search/user-by-id/${id}`)
labelSpan.innerHTML = ` <i>(${await response.text()})</i>`
}
}
if (publicField && searchForm && searchField) {
publicField.name = "#{@custom_search}"
searchField.name = "#{@custom_search}"
const updateSearch = async () => {
const search = userIdField.value
const params = new URLSearchParams(window.location.search)
publicField.value = params.get("#{@custom_search}")
updateLabel(search)
const searchInput = document.createElement("input")
searchInput.name = "search"
searchInput.type = "hidden"
searchInput.value = ""
const response = await fetch("/crm/billing/search/user", {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify({ search: search })
})
searchForm.appendChild(searchInput)
const list = await response.json()
const options =
list.map(([label, value]) => {
const option = document.createElement("option")
option.setAttribute("label", label)
option.textContent = value
return option
})
dataList.replaceChildren(...options)
}
updateLabel(userIdField.value)
userIdField.addEventListener("input", async (e) => {
if (updateAction) {
clearTimeout(updateAction)
updateAction = null
}
updateAction = setTimeout(() => updateSearch(), CHECK_INTERVAL)
})
})()
</script>
""")
]
end

def javascripts(%{assigns: %{context: "billing", resource: "enterprise_plan", changeset: %{}}}) do
[
"""),
Phoenix.HTML.raw("""
<script type="text/javascript">
(() => {
Expand Down Expand Up @@ -153,6 +186,36 @@ defmodule Plausible.CrmExtensions do
""")
]
end

def javascripts(%{assigns: %{context: context}})
when context in ["sites", "billing"] do
[
Phoenix.HTML.raw("""
<script type="text/javascript">
(() => {
const publicField = document.querySelector("#kaffy-search-field")
const searchForm = document.querySelector("#kaffy-filters-form")
const searchField = document.querySelector("#kaffy-filter-search")
if (publicField && searchForm && searchField) {
publicField.name = "#{@custom_search}"
searchField.name = "#{@custom_search}"
const params = new URLSearchParams(window.location.search)
publicField.value = params.get("#{@custom_search}")
const searchInput = document.createElement("input")
searchInput.name = "search"
searchInput.type = "hidden"
searchInput.value = ""
searchForm.appendChild(searchInput)
}
})()
</script>
""")
]
end
end

def javascripts(_) do
Expand Down
57 changes: 57 additions & 0 deletions lib/plausible_web/controllers/admin_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ defmodule PlausibleWeb.AdminController do
use PlausibleWeb, :controller
use Plausible

import Ecto.Query

alias Plausible.Repo
alias Plausible.Teams

def usage(conn, params) do
Expand Down Expand Up @@ -71,6 +74,60 @@ defmodule PlausibleWeb.AdminController do
|> send_resp(200, json_response)
end

def user_by_id(conn, params) do
id = params["user_id"]

entry =
Repo.one(
from u in Plausible.Auth.User,
where: u.id == ^id,
select: fragment("concat(?, ?, ?, ?)", u.name, " (", u.email, ")")
) || ""

conn
|> send_resp(200, entry)
end

def user_search(conn, params) do
search =
(params["search"] || "")
|> String.trim()

choices =
if search != "" do
term =
search
|> String.replace("%", "\%")
|> String.replace("_", "\_")

term = "%#{term}%"

user_id =
case Integer.parse(search) do
{id, ""} -> id
_ -> 0
end

if user_id != 0 do
[]
else
Repo.all(
from u in Plausible.Auth.User,
where: u.id == ^user_id or ilike(u.name, ^term) or ilike(u.email, ^term),
order_by: [u.name, u.id],
select: [fragment("concat(?, ?, ?, ?)", u.name, " (", u.email, ")"), u.id],
limit: 20
)
end
else
[]
end

conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(choices))
end

defp usage_and_limits_html(team, usage, limits, embed?) do
content = """
<ul>
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 @@ -92,6 +92,8 @@ defmodule PlausibleWeb.Router do
pipe_through :flags
get "/auth/user/:user_id/usage", AdminController, :usage
get "/billing/user/:user_id/current_plan", AdminController, :current_plan
get "/billing/search/user-by-id/:user_id", AdminController, :user_by_id
post "/billing/search/user", AdminController, :user_search
end
end

Expand Down

0 comments on commit 3e4d59d

Please sign in to comment.