Skip to content

Commit 558124d

Browse files
committed
Add custom datetime patterns functionality
1 parent 2f42b32 commit 558124d

9 files changed

Lines changed: 175 additions & 59 deletions

File tree

apps/dashboard/assets/css/custom.scss

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -132,42 +132,49 @@ body {
132132
text-overflow: ellipsis;
133133
}
134134

135-
#date-time-patterns-container {
136-
padding-top: 10px;
137-
height: 300px;
138-
overflow-y: auto;
139-
scroll-behavior: smooth;
140-
scrollbar-gutter: stable; // Reserve space for scrollbar to avoid UI shifting when scrollbar appears
141-
scrollbar-color: #d4aa70 #e4e4e4; // Works for non-chromium (tested on firfox)
142-
}
143-
144-
#db-attrs-container {
135+
// Custom scroll bar implementation
136+
.scrollable-container {
145137
padding: 10px;
146-
height: 300px;
147138
overflow-y: auto;
148139
scroll-behavior: smooth;
149140
scrollbar-gutter: stable; // Reserve space for scrollbar to avoid UI shifting when scrollbar appears
150141
scrollbar-color: #d4aa70 #e4e4e4; // Works for non-chromium (tested on firfox)
151142

152-
li { display: list-item; }
153-
154-
.db-attr-index {
155-
padding: 2px;
156-
}
157-
158143
// Custom Scrollbar css works only for chromium based browser
159144
&::-webkit-scrollbar {
160145
width: 10px;
161146
}
162-
147+
163148
&::-webkit-scrollbar-track {
164149
background-color: #e4e4e4;
165150
border-radius: 100px;
166151
}
167-
152+
168153
&::-webkit-scrollbar-thumb {
169154
border-radius: 100px;
170155
background-color: #d4aa70;
171156
box-shadow: inset 2px 2px 5px 0 rgba(#fff, 0.5);
172157
}
173158
}
159+
160+
#db-attrs-container {
161+
height: 300px;
162+
163+
li {
164+
display: list-item;
165+
}
166+
167+
.db-attr-index {
168+
padding: 2px;
169+
}
170+
}
171+
172+
#date-patterns-container, #date-time-patterns-container {
173+
height: 250px;
174+
}
175+
176+
// Matched Custom Date/DateTime pattern input glow
177+
.matched-input {
178+
border-color: green;
179+
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px green;
180+
}

apps/dashboard/assets/js/app.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,22 @@ let hooks = {
1919

2020
let liveSocket = new LiveSocket("/live", Socket, { hooks, params: { _csrf_token: csrfToken } });
2121
liveSocket.connect();
22-
23-
// expose liveSocket on window for web console debug logs and latency simulation:
24-
// >> liveSocket.enableDebug()
25-
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
26-
// >> liveSocket.disableLatencySim()
2722
window.liveSocket = liveSocket;
2823

29-
// Listen for scroll events and auto scroll to end
24+
// Listen for phx "scroll-to-bottom" events and scroll to end of given element id
3025
window.addEventListener(
3126
"phx:scroll-to-bottom",
3227
e => {
3328
const element = document.getElementById(e.detail.id);
3429
element.scrollTop = element.scrollHeight;
3530
}
3631
)
32+
33+
// Listen for phx "scroll-into-view" events and scroll the given element id into view
34+
window.addEventListener(
35+
"phx:scroll-into-view",
36+
e => {
37+
const element = document.getElementById(e.detail.id);
38+
element.scrollIntoView({behavior: "smooth", block: "nearest"});
39+
}
40+
)

apps/dashboard/lib/dashboard/config.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defmodule DashBoard.Config do
3030

3131
# Misc
3232
field(:csv_count, :integer)
33+
field(:date_time_trial, :string)
3334

3435
embeds_many(:db_attrs, DashBoard.DbAttribute, on_replace: :delete)
3536
embeds_many(:date_patterns, DashBoard.DatePattern, on_replace: :delete)

apps/dashboard/lib/dashboard/helpers.ex

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ defmodule Dashboard.Helpers do
33

44
defguard present?(value) when value != nil and value != ""
55

6+
def match_date_time(changeset, date_time_sample) do
7+
date_index =
8+
changeset
9+
|> Ecto.Changeset.get_field(:date_patterns, [])
10+
|> get_matching_pattern_index(date_time_sample)
11+
12+
if is_nil(date_index) do
13+
date_time_index =
14+
changeset
15+
|> Ecto.Changeset.get_field(:date_time_patterns, [])
16+
|> get_matching_pattern_index(date_time_sample)
17+
18+
if is_nil(date_time_index), do: false, else: {:date_time, date_time_index}
19+
else
20+
if is_nil(date_index), do: false, else: {:date, date_index}
21+
end
22+
end
23+
624
def create_db_url(configs, hide_password \\ true)
725

826
def create_db_url(
@@ -37,4 +55,13 @@ defmodule Dashboard.Helpers do
3755
do: password |> String.split("") |> Enum.map(fn _ -> "•" end) |> Enum.join("")
3856

3957
defp hide_password(password, false), do: password
58+
59+
defp get_matching_pattern_index(patterns, string) do
60+
Enum.find_index(patterns, fn pattern_struct ->
61+
case Timex.parse(string, pattern_struct.pattern) do
62+
{:ok, _} -> true
63+
{:error, _} -> false
64+
end
65+
end)
66+
end
4067
end

apps/dashboard/lib/dashboard_web/live/config_live.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,7 @@ defmodule DashboardWeb.Live.ConfigLive do
281281
<%= hidden_input(assoc_form, :id) %>w
282282
<%= hidden_input(assoc_form, :pattern) %>
283283
<% end %>
284-
<% end %>
285284
286-
<%= if @modal != "add-datepatterns" do %>
287285
<%= inputs_for f, :date_patterns, fn assoc_form -> %>
288286
<%= hidden_input(assoc_form, :id) %>w
289287
<%= hidden_input(assoc_form, :pattern) %>
@@ -292,7 +290,7 @@ defmodule DashboardWeb.Live.ConfigLive do
292290
293291
<%= case @modal do %>
294292
<% "add-more-db-attrs" -> %> <.db_attrs_modal id="db_attrs_modal" form={f} changeset={@changeset} />
295-
<% "add-date-time-patterns" -> %> <.date_time_patterns_modal id="date_time_patterns_modal" form={f} changeset={@changeset} />
293+
<% "add-date-time-patterns" -> %> <.date_time_patterns_modal id="date_time_patterns_modal" form={f} changeset={@changeset} matching_date_time={@matching_date_time}/>
296294
<% _ -> %>
297295
<% end %>
298296
</.form>

apps/dashboard/lib/dashboard_web/live/main_live.ex

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule DashboardWeb.Live.MainLive do
22
use DashboardWeb, :live_view
3+
import Dashboard.Helpers
34
alias DashBoard.{Config, DbAttribute}
45
alias Csv2sql.Database.ConnectionTest
56

@@ -14,7 +15,8 @@ defmodule DashboardWeb.Live.MainLive do
1415
path_validator_debouncer: nil,
1516
db_connection_debouncer: nil,
1617
db_connection_established: false,
17-
changeset: Ecto.Changeset.change(%DashBoard.Config{})
18+
changeset: Ecto.Changeset.change(%DashBoard.Config{}),
19+
matching_date_time: nil
1820
)}
1921
end
2022

@@ -34,7 +36,7 @@ defmodule DashboardWeb.Live.MainLive do
3436
end
3537

3638
@impl true
37-
def handle_event("validate-and-save", attrs, socket) do
39+
def handle_event("validate-and-save", attrs, ~M{assigns} = socket) do
3840
args = Map.get(attrs, "config", %{})
3941

4042
socket =
@@ -46,6 +48,7 @@ defmodule DashboardWeb.Live.MainLive do
4648
# DB connection checker is expensive and returns result to caller process with delay
4749
# so we don't do this validation on changeset level
4850
|> db_connection_checker(args)
51+
|> update_matching_date_time(attrs)
4952

5053
{:noreply, socket}
5154
end
@@ -95,13 +98,16 @@ defmodule DashboardWeb.Live.MainLive do
9598
updated_changeset =
9699
Ecto.Changeset.put_embed(assigns.changeset, association, updated_association)
97100

98-
{:noreply, assign(socket, changeset: updated_changeset)}
101+
{:noreply,
102+
socket
103+
|> assign(changeset: updated_changeset)
104+
|> update_matching_date_time()}
99105
end
100106

101107
@impl true
102108
def handle_info(:check_db_connection, ~M{assigns} = socket) do
103109
with(
104-
db_url = Dashboard.Helpers.create_db_url(assigns.changeset.changes, false),
110+
db_url = create_db_url(assigns.changeset.changes, false),
105111
true <- not ("NA" == db_url),
106112
db_type <- Ecto.Changeset.get_field(assigns.changeset, :db_type),
107113
false <- is_nil(db_type),
@@ -158,4 +164,23 @@ defmodule DashboardWeb.Live.MainLive do
158164
Ecto.Changeset.get_field(changeset, :db_host) != Map.get(args, "db_host") ||
159165
Ecto.Changeset.get_field(changeset, :db_name) != Map.get(args, "db_name")
160166
end
167+
168+
defp update_matching_date_time(~M{assigns} = socket, attrs \\ %{}) do
169+
date_time_sample =
170+
get_in(attrs, ["config", "date_time_trial"]) ||
171+
Ecto.Changeset.get_field(assigns.changeset, :date_time_trial)
172+
173+
case match_date_time(assigns.changeset, date_time_sample) do
174+
{type, index} ->
175+
176+
socket
177+
|> assign(matching_date_time: {type, index})
178+
|> push_event("scroll-into-view", %{
179+
id: "config_#{type}_patterns_#{index}_pattern"
180+
})
181+
182+
false ->
183+
assign(socket, matching_date_time: nil)
184+
end
185+
end
161186
end

apps/dashboard/lib/dashboard_web/live/modals/date_time_patterns_modal.ex

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule DashboardWeb.Live.Modal.DateTimePatternsModal do
22
use DashboardWeb, :component
3+
import Dashboard.Helpers
34

45
@date_time_pattern_hints [
56
{"{YYYY}", "full year number (0..9999)"},
@@ -18,14 +19,17 @@ defmodule DashboardWeb.Live.Modal.DateTimePatternsModal do
1819
]
1920

2021
def date_time_patterns_modal(assigns) do
21-
custom_date_patterns_added =
22+
custom_date_time_patterns_added =
2223
assigns.changeset |> Ecto.Changeset.get_field(:date_time_patterns, []) |> length() != 0
2324

25+
custom_date_patterns_added =
26+
assigns.changeset |> Ecto.Changeset.get_field(:date_patterns, []) |> length() != 0
27+
2428
~H"""
2529
<DashboardWeb.Live.Modals.modal title="Add additional Date Time patterns">
2630
<div>
2731
<div class="fst-italic mt-2 ms-2">
28-
Add datetime patterns
32+
Add date patterns
2933
</div>
3034
3135
<!-- TODO: Make this better: data list only works on double click -->
@@ -37,28 +41,65 @@ defmodule DashboardWeb.Live.Modal.DateTimePatternsModal do
3741
3842
<div class="container">
3943
<div class="row">
40-
<div class="col-6 border" id="date-time-patterns-container">
41-
<%= if custom_date_patterns_added do %>
42-
<%= for {date_time_patterns_form, index} <- Enum.with_index(inputs_for(@form, :date_time_patterns), 1) do %>
43-
<div class="d-flex flex-row">
44-
<div class="input-group mb-3">
45-
<span class="input-group-text fw-bold pe-2 pt-1"><%= index %>.</span>
46-
<%= text_input date_time_patterns_form, :pattern, class: "form-control", placeholder: "Pattern", list: "date-time-patterns-suggestions" %>
44+
<div class="col-6">
45+
<div class="border">
46+
<div class="scrollable-container" id="date-patterns-container" class="border">
47+
<%= if custom_date_patterns_added do %>
48+
<%= for {date_patterns_form, index} <- Enum.with_index(inputs_for(@form, :date_patterns), 1) do %>
49+
<div class="d-flex flex-row">
50+
<div class="input-group mb-3">
51+
<span class="input-group-text fw-bold pe-2 pt-1"><%= index %>.</span>
52+
<%= text_input date_patterns_form, :pattern, class: input_class(index, :date, @matching_date_time), placeholder: "Pattern", list: "date-patterns-suggestions" %>
53+
</div>
54+
55+
<%= hidden_input(date_patterns_form, :id) %>
56+
57+
<div role="button" phx-click="remove-date-pattern" phx-value-attrid={Ecto.Changeset.get_field(date_patterns_form.source, :id)}>
58+
<IconSvg.remove_icon class="ms-2 pt-1"/>
59+
</div>
60+
</div>
61+
<% end %>
62+
<% else %>
63+
<div class="text-center mt-4 pt-4">
64+
<div> <IconSvg.empty_icon {%{height: 100, width: 100}}/> </div>
65+
<div class="fw-bold fst-italic font-monospace text-danger"> No custom date patterns added! </div>
4766
</div>
67+
<% end %>
68+
</div>
69+
<div class="add-pattern" phx-click="add-new-date-pattern">
70+
<IconSvg.add_icon {%{width: 28}} />
71+
<span> Add datetime pattern </span>
72+
</div>
73+
</div>
74+
<div class="border">
75+
<div class="scrollable-container" id="date-time-patterns-container">
76+
<%= if custom_date_time_patterns_added do %>
77+
<%= for {date_time_patterns_form, index} <- Enum.with_index(inputs_for(@form, :date_time_patterns), 1) do %>
78+
<div class="d-flex flex-row">
79+
<div class="input-group mb-3">
80+
<span class="input-group-text fw-bold pe-2 pt-1"><%= index %>.</span>
81+
<%= text_input date_time_patterns_form, :pattern, class: input_class(index, :date_time, @matching_date_time), placeholder: "Pattern", list: "date-time-patterns-suggestions" %>
82+
</div>
4883
49-
<%= hidden_input(date_time_patterns_form, :id) %>
84+
<%= hidden_input(date_time_patterns_form, :id) %>
5085
51-
<div role="button" phx-click="remove-date-time-pattern" phx-value-attrid={Ecto.Changeset.get_field(date_time_patterns_form.source, :id)}>
52-
<IconSvg.remove_icon class="ms-2 pt-1"/>
86+
<div role="button" phx-click="remove-date-time-pattern" phx-value-attrid={Ecto.Changeset.get_field(date_time_patterns_form.source, :id)}>
87+
<IconSvg.remove_icon class="ms-2 pt-1"/>
88+
</div>
89+
</div>
90+
<% end %>
91+
<% else %>
92+
<div class="text-center mt-4 pt-4">
93+
<div> <IconSvg.empty_icon {%{height: 100, width: 100}}/> </div>
94+
<div class="fw-bold fst-italic font-monospace text-danger"> No custom date time patterns added! </div>
5395
</div>
54-
</div>
55-
<% end %>
56-
<% else %>
57-
<div class="text-center mt-4 pt-4">
58-
<div> <IconSvg.empty_icon {%{height: 100, width: 100}}/> </div>
59-
<div class="fw-bold fst-italic font-monospace text-danger"> No custom date time patterns added! </div>
96+
<% end %>
97+
</div>
98+
<div class="add-pattern" phx-click="add-new-date-time-pattern">
99+
<IconSvg.add_icon {%{width: 28}} />
100+
<span> Add datetime pattern </span>
60101
</div>
61-
<% end %>
102+
</div>
62103
</div>
63104
<div class="col-6 border">
64105
<p>
@@ -75,10 +116,20 @@ defmodule DashboardWeb.Live.Modal.DateTimePatternsModal do
75116
<% end %>
76117
</div>
77118
<p> *Fractional seconds and timezones are not supported. </p>
78-
</div>
79-
<div class="col-8 add-link text-center">
80-
<IconSvg.add_icon {%{width: 28}} />
81-
<span phx-click="add-new-date-time-pattern"> Add datetime pattern </span>
119+
120+
<div id="date-time-try-it">
121+
<div class="d-flex flex-row">
122+
<!-- TODO: Fix tolltip title gone on hook init -->
123+
<.popup
124+
title="Enter a sample date or date time to check if it matches any added custom patterns"
125+
id="date-time-try-it-hint"/>
126+
<label for="date-time-trial"> Enter sample date time </label>
127+
</div>
128+
<%= if present?(Ecto.Changeset.get_field(@changeset, :date_time_trial)) && is_nil(@matching_date_time) do %>
129+
<span> No matching patterns found </span>
130+
<% end %>
131+
<%= text_input @form, :date_time_trial, class: "form-control", placeholder: "Pattern", id: "date-time-trial" %>
132+
</div>
82133
</div>
83134
</div>
84135
</div>
@@ -88,4 +139,7 @@ defmodule DashboardWeb.Live.Modal.DateTimePatternsModal do
88139
end
89140

90141
defp date_time_pattern_hints, do: @date_time_pattern_hints
142+
143+
defp input_class(index, type, {match_type, match_index}) when type == match_type and index == match_index + 1, do: "form-control matched-input"
144+
defp input_class(_index, _type, _), do: "form-control"
91145
end

apps/dashboard/lib/dashboard_web/live/modals/db_attrs_modal.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defmodule DashboardWeb.Live.Modal.DbAttributesModal do
2121
<option value="socket"/>
2222
</datalist>
2323
24-
<div id="db-attrs-container">
24+
<div class="scrollable-container" id="db-attrs-container">
2525
<%= if db_attrs_added do %>
2626
<%= for {db_attrs_form, index} <- Enum.with_index(inputs_for(@form, :db_attrs), 1) do %>
2727
<div class="d-flex flex-row">

0 commit comments

Comments
 (0)