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

Reimplement user search in enterprise plans CRM and fix plan prefill #4913

Merged
merged 1 commit into from
Dec 17, 2024
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: 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
Loading