Skip to content

Commit 558352c

Browse files
aerosolzoldar
andauthored
Implement Team setup CTA+view (#4960)
* spike * wip omg * Add option to clear combobox dropdown on selection * Extend combobox on selection made callback * Remove IO.inspect * Add more guests via seeds * Implement basic UX for arbitrary e-mail addresses * Save team name on change * Don't crash on invalid team name input * Enable :teams flag via seeds * Implement changing roles * Display avatars in combobox when selecting member candidates * Reduce noise * Add 'Create team' button to the layout * Revert "Extend combobox on selection made callback" This reverts commit 874d566. * Fix seeds * Make email submittion optional in InviteToTeam service * Implement finalising team setup (WIP) * Revert "Display avatars in combobox when selecting member candidates" This reverts commit ff1a30d. * Use regular redirect to preserve flash * Check team's setup complete status on mount * Add `dev` param, allowing redirect skip on setup complete * Bootstrap test module * Add sample test touching on combobox in team setup * WIP: testing team setup LV * Fixup test * Remove unused bindings * Add another minor test * Test removing a member candidate * Finish up remaining tests * Rename main module * Put team setup view behind a feature flag * Update main nav dropdown * Update ComboBox tests * Fix CandidatesTest regression * Bring back OG combo-box.js * Fix dark mode red * Minor styling fixes * Extract setup_team function into context * Remove Floki calls, use Plausible.HTML proxy instead * Make credo happy * Use path helpers instead of hardcoded paths * Fix formatting * Revert "Merge branch 'master' into setup-teams-01" This reverts commit bc436c5, reversing changes made to 2eb128d. * Reapply "Merge branch 'master' into setup-teams-01" This reverts commit c7ebdd2. * Alter clear_on_select behavior * Revert "Alter clear_on_select behavior" This reverts commit deb20c4. * Look up guests with distinct:true * Revert "Look up guests with distinct:true" This reverts commit 352d271. * Look up guests with distinct:true * Try something dumb * huh? * Bring back problematic changes * Make combobox dropdown open state and spinner behave on form update * Don't explicitly send update on live component attribute update * Don't explicitly send updates to options in funnel combobox either * Revert "Temporarily disable combobox spinner (#4971)" This reverts commit 3abf974. * Fix formatting * Set 'id' on element with phx-update=ignore * Rework options and suggestions setup in combobox * Fix suggestions for async * Fix formatting --------- Co-authored-by: Adrian Gruntkowski <[email protected]>
1 parent 5427a7a commit 558352c

File tree

17 files changed

+824
-32
lines changed

17 files changed

+824
-32
lines changed

extra/lib/plausible_web/live/funnel_settings/form.ex

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
9494
end
9595
}
9696
id={"step-#{step_idx}"}
97-
options={reject_already_selected("step-#{step_idx}", @goals, @selections_made)}
97+
options={reject_already_selected(@goals, @selections_made)}
9898
/>
9999
</div>
100100
@@ -385,16 +385,13 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
385385
Map.delete(selections_made, step_input_id)
386386
end
387387

388-
defp reject_already_selected(combo_box, goals, selections_made) do
388+
defp reject_already_selected(goals, selections_made) do
389389
selection_ids =
390390
Enum.map(selections_made, fn
391391
{_, %{id: goal_id}} -> goal_id
392392
end)
393393

394-
result = Enum.reject(goals, fn {goal_id, _} -> goal_id in selection_ids end)
395-
396-
send_update(PlausibleWeb.Live.Components.ComboBox, id: combo_box, suggestions: result)
397-
result
394+
Enum.reject(goals, fn {goal_id, _} -> goal_id in selection_ids end)
398395
end
399396

400397
defp find_preselected(%Funnel{} = funnel, false, idx) do

lib/plausible/teams.ex

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,39 @@ defmodule Plausible.Teams do
261261
)
262262
end
263263

264+
def setup_team(team, candidates) do
265+
inviter = Repo.preload(team, :owner).owner
266+
267+
setup_team_fn = fn {{email, _name}, role} ->
268+
case Teams.Invitations.InviteToTeam.invite(team, inviter, email, role, send_email?: false) do
269+
{:ok, invitation} -> invitation
270+
{:error, error} -> Repo.rollback(error)
271+
end
272+
end
273+
274+
result =
275+
Repo.transaction(fn ->
276+
team
277+
|> Teams.Team.setup_changeset()
278+
|> Repo.update!()
279+
280+
Enum.map(candidates, setup_team_fn)
281+
end)
282+
283+
case result do
284+
{:ok, invitations} ->
285+
Enum.each(invitations, fn invitation ->
286+
invitee = Plausible.Auth.find_user_by(email: invitation.email)
287+
Teams.Invitations.InviteToTeam.send_invitation_email(invitation, invitee)
288+
end)
289+
290+
{:ok, invitations}
291+
292+
{:error, {:over_limit, _}} = error ->
293+
error
294+
end
295+
end
296+
264297
defp create_my_team(user) do
265298
team =
266299
"My Team"

lib/plausible/teams/invitations/candidates.ex

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,25 @@ defmodule Plausible.Teams.Invitations.Candidates do
2323
inner_join: u in assoc(tm, :user),
2424
where: gm.site_id in ^all_site_ids,
2525
where: ilike(u.email, ^term) or ilike(u.name, ^term),
26-
where: u.id not in ^for(u <- exclude, do: u.id),
26+
where: u.email not in ^exclude,
2727
order_by: [asc: u.id],
2828
select: u,
2929
distinct: true,
3030
limit: ^limit
3131
)
3232
end
33+
34+
def get_site_guest(%Teams.Team{} = team, email) do
35+
all_site_ids = Teams.owned_sites_ids(team)
36+
37+
Repo.one(
38+
from gm in GuestMembership,
39+
inner_join: tm in assoc(gm, :team_membership),
40+
inner_join: u in assoc(tm, :user),
41+
where: gm.site_id in ^all_site_ids,
42+
where: u.email == ^email,
43+
distinct: true,
44+
select: u
45+
)
46+
end
3347
end

lib/plausible/teams/invitations/invite_to_team.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ defmodule Plausible.Teams.Invitations.InviteToTeam do
3535
),
3636
{:ok, invitation} <-
3737
Teams.Invitations.invite(team, invitee_email, role, inviter) do
38-
send_invitation_email(invitation, invitee)
38+
if Keyword.get(opts, :send_email?, true) do
39+
send_invitation_email(invitation, invitee)
40+
end
3941

4042
{:ok, invitation}
4143
end
@@ -45,7 +47,7 @@ defmodule Plausible.Teams.Invitations.InviteToTeam do
4547
raise "Invalid role passed: #{inspect(role)}"
4648
end
4749

48-
defp send_invitation_email(invitation, invitee) do
50+
def send_invitation_email(invitation, invitee) do
4951
invitation
5052
|> Repo.preload([:team, :inviter])
5153
|> Teams.Invitations.send_invitation_email(invitee)

lib/plausible/teams/team.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ defmodule Plausible.Teams.Team do
6161
|> validate_required(:name)
6262
end
6363

64+
def setup_changeset(team) do
65+
now = NaiveDateTime.utc_now(:second)
66+
67+
team
68+
|> change(
69+
setup_complete: true,
70+
setup_at: now
71+
)
72+
end
73+
6474
def start_trial(team, today \\ Date.utc_today()) do
6575
trial_expiry = trial_expiry(today)
6676

lib/plausible_web/live/components/combo_box.ex

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
5050
if connected?(socket) do
5151
socket
5252
|> assign_options()
53-
|> assign_suggestions()
5453
else
5554
socket
5655
end
56+
|> assign_suggestions(assigns[:suggestions])
5757

5858
{:ok, socket}
5959
end
@@ -68,18 +68,15 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
6868
attr(:suggest_fun, :any, required: true)
6969
attr(:suggestions_limit, :integer)
7070
attr(:class, :string, default: "")
71+
attr(:clear_on_select, :boolean, default: false)
7172
attr(:required, :boolean, default: false)
7273
attr(:creatable, :boolean, default: false)
74+
attr(:creatable_prompt, :string, default: "Create")
7375
attr(:errors, :list, default: [])
7476
attr(:async, :boolean, default: Mix.env() != :test)
7577
attr(:on_selection_made, :any)
7678

7779
def render(assigns) do
78-
assigns =
79-
assign_new(assigns, :suggestions, fn ->
80-
Enum.take(assigns.options, suggestions_limit(assigns))
81-
end)
82-
8380
~H"""
8481
<div
8582
id={"input-picker-main-#{@id}"}
@@ -115,7 +112,12 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
115112
/>
116113
117114
<.spinner class="spinner hidden absolute inset-y-3 right-8" />
118-
<.spinner x-show="selectionInProgress" class="spinner absolute inset-y-3 right-8" />
115+
<.spinner
116+
id={"selection-in-progress-#{@id}"}
117+
phx-update="ignore"
118+
x-show="selectionInProgress"
119+
class="spinner absolute inset-y-3 right-8"
120+
/>
119121
120122
<.dropdown_anchor id={@id} />
121123
@@ -136,6 +138,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
136138
target={@myself}
137139
creatable={@creatable}
138140
display_value={@display_value}
141+
creatable_prompt={@creatable_prompt}
139142
/>
140143
</div>
141144
</div>
@@ -170,6 +173,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
170173
attr(:suggest_fun, :any, required: true)
171174
attr(:target, :any)
172175
attr(:creatable, :boolean, required: true)
176+
attr(:creatable_prompt, :string, default: nil)
173177
attr(:display_value, :string, required: true)
174178

175179
def combo_dropdown(assigns) do
@@ -180,6 +184,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
180184
x-show="isOpen"
181185
x-ref="suggestions"
182186
class="text-sm w-full dropdown z-50 absolute mt-1 max-h-60 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-900"
187+
style="display: none;"
183188
>
184189
<.option
185190
:if={display_creatable_option?(assigns)}
@@ -189,6 +194,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
189194
target={@target}
190195
ref={@ref}
191196
creatable
197+
creatable_prompt={@creatable_prompt}
192198
/>
193199
194200
<.option
@@ -229,6 +235,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
229235
attr(:target, :any)
230236
attr(:idx, :integer, required: true)
231237
attr(:creatable, :boolean, default: false)
238+
attr(:creatable_prompt, :string, required: false)
232239

233240
def option(assigns) do
234241
assigns = assign(assigns, :suggestions_limit, suggestions_limit(assigns))
@@ -245,15 +252,15 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
245252
>
246253
<a
247254
x-ref={"dropdown-#{@ref}-option-#{@idx}"}
248-
x-on:click={not @creatable && "selectionInProgress = false"}
255+
x-on:click={not @creatable && "selectionInProgress = true"}
249256
phx-click={select_option(@ref, @submit_value, @display_value)}
250257
phx-value-submit-value={@submit_value}
251258
phx-value-display-value={@display_value}
252259
phx-target={@target}
253260
class="block truncate py-2 px-3"
254261
>
255262
<%= if @creatable do %>
256-
Create "{@display_value}"
263+
{@creatable_prompt} "{@display_value}"
257264
<% else %>
258265
{@display_value}
259266
<% end %>
@@ -317,6 +324,13 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
317324
defp do_select(socket, submit_value, display_value) do
318325
id = socket.assigns.id
319326

327+
display_value =
328+
if socket.assigns[:clear_on_select] do
329+
""
330+
else
331+
display_value
332+
end
333+
320334
socket =
321335
socket
322336
|> push_event("update-value", %{id: id, value: display_value, fire: false})
@@ -350,15 +364,17 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
350364
end)
351365
end
352366

353-
defp assign_suggestions(socket) do
354-
if socket.assigns[:suggestions] do
355-
assign(
356-
socket,
357-
suggestions: Enum.take(socket.assigns.suggestions, suggestions_limit(socket.assigns))
358-
)
359-
else
360-
socket
361-
end
367+
defp assign_suggestions(socket, nil = _suggestions_from_update) do
368+
suggestions =
369+
socket.assigns
370+
|> Map.get(:options, [])
371+
|> Enum.take(suggestions_limit(socket.assigns))
372+
373+
assign(socket, suggestions: suggestions)
374+
end
375+
376+
defp assign_suggestions(socket, _suggestions_from_update) do
377+
socket
362378
end
363379

364380
defp select_default(socket) do

lib/plausible_web/live/components/form.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ defmodule PlausibleWeb.Live.Components.Form do
1818
<.input name="my-input" errors={["oh no!"]} />
1919
"""
2020

21-
@default_input_class "text-sm text-gray-900 dark:text-white dark:bg-gray-900 block pl-3.5 py-2.5 border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 rounded-md"
21+
@default_input_class "text-sm text-gray-900 dark:text-white dark:bg-gray-900 block pl-3.5 py-2 border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 rounded-md"
2222

2323
attr(:id, :any, default: nil)
2424
attr(:name, :any)

0 commit comments

Comments
 (0)