From e7924532be2b91c212232eab110406a64865cd67 Mon Sep 17 00:00:00 2001 From: Dmitry Popov Date: Fri, 8 Nov 2024 01:39:44 +0400 Subject: [PATCH] feat(Connections): Add connection mark EOL time (#56) --- .../components/Connections/Connections.tsx | 62 +++++++--- .../hooks/Mapper/types/connectionPassages.ts | 4 + assets/js/hooks/Mapper/types/mapHandlers.ts | 1 + lib/wanderer_app/map/map_server.ex | 6 + lib/wanderer_app/map/map_server_impl.ex | 114 +++++++++++------- .../map_connections_event_handler.ex | 25 ++++ .../live/maps/map_event_handler.ex | 1 + ...0429_install_ash-functions_extension_4.exs | 54 --------- 8 files changed, 151 insertions(+), 116 deletions(-) delete mode 100644 priv/repo/migrations/20240922090429_install_ash-functions_extension_4.exs diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/Connections/Connections.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/Connections/Connections.tsx index 83f449ae..b571d5d2 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/Connections/Connections.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/Connections/Connections.tsx @@ -1,13 +1,20 @@ import classes from './Connections.module.scss'; import { Sidebar } from 'primereact/sidebar'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState, useCallback } from 'react'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller'; import clsx from 'clsx'; -import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types'; +import { + ConnectionOutput, + ConnectionInfoOutput, + OutCommand, + Passage, + SolarSystemConnection, +} from '@/hooks/Mapper/types'; import { PassageCard } from './PassageCard'; import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit'; import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts'; +import { TimeAgo } from '@/hooks/Mapper/components/ui-kit'; const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime(); @@ -69,25 +76,44 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => { }, [connections, selectedConnection]); const [passages, setPassages] = useState([]); + const [info, setInfo] = useState(null); + + const loadInfo = useCallback( + async (connection: SolarSystemConnection) => { + const result = await outCommand({ + type: OutCommand.getConnectionInfo, + data: { + from: connection.source, + to: connection.target, + }, + }); + + setInfo(result); + }, + [outCommand], + ); + + const loadPassages = useCallback( + async (connection: SolarSystemConnection) => { + const result = await outCommand({ + type: OutCommand.getPassages, + data: { + from: connection.source, + to: connection.target, + }, + }); + + setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at))); + }, + [outCommand], + ); useEffect(() => { if (!selectedConnection) { return; } - - const loadInfo = async () => { - const result = await outCommand({ - type: OutCommand.getPassages, - data: { - from: selectedConnection.source, - to: selectedConnection.target, - }, - }); - - setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at))); - }; - - loadInfo(); + loadInfo(selectedConnection); + loadPassages(selectedConnection); }, [selectedConnection]); const approximateMass = useMemo(() => { @@ -132,6 +158,10 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => { {kgToTons(approximateMass)} + + {info?.marl_eol_time ? : ' unknown '} + +
diff --git a/assets/js/hooks/Mapper/types/connectionPassages.ts b/assets/js/hooks/Mapper/types/connectionPassages.ts index cfafa6bc..3a56d1f4 100644 --- a/assets/js/hooks/Mapper/types/connectionPassages.ts +++ b/assets/js/hooks/Mapper/types/connectionPassages.ts @@ -11,6 +11,10 @@ export type Passage = { character: PassageLimitedCharacterType; }; +export type ConnectionInfoOutput = { + marl_eol_time: string; +}; + export type ConnectionOutput = { passages: Passage[]; }; diff --git a/assets/js/hooks/Mapper/types/mapHandlers.ts b/assets/js/hooks/Mapper/types/mapHandlers.ts index 63dce60f..1f56dabc 100644 --- a/assets/js/hooks/Mapper/types/mapHandlers.ts +++ b/assets/js/hooks/Mapper/types/mapHandlers.ts @@ -118,6 +118,7 @@ export enum OutCommand { getCharacterJumps = 'get_character_jumps', getSignatures = 'get_signatures', getSystemStaticInfos = 'get_system_static_infos', + getConnectionInfo = 'get_connection_info', updateConnectionTimeStatus = 'update_connection_time_status', updateConnectionMassStatus = 'update_connection_mass_status', updateConnectionShipSizeType = 'update_connection_ship_size_type', diff --git a/lib/wanderer_app/map/map_server.ex b/lib/wanderer_app/map/map_server.ex index 1a6a8e49..b1ae6950 100644 --- a/lib/wanderer_app/map/map_server.ex +++ b/lib/wanderer_app/map/map_server.ex @@ -183,6 +183,12 @@ defmodule WandererApp.Map.Server do |> map_pid! |> GenServer.cast({&Impl.delete_connection/2, [connection_info]}) + def get_connection_info(map_id, connection_info) when is_binary(map_id), + do: + map_id + |> map_pid! + |> GenServer.call({&Impl.get_connection_info/2, [connection_info]}, :timer.minutes(1)) + def update_connection_time_status(map_id, connection_info) when is_binary(map_id), do: map_id diff --git a/lib/wanderer_app/map/map_server_impl.ex b/lib/wanderer_app/map/map_server_impl.ex index 8cb0f5e3..06a0e4cb 100644 --- a/lib/wanderer_app/map/map_server_impl.ex +++ b/lib/wanderer_app/map/map_server_impl.ex @@ -153,8 +153,8 @@ defmodule WandererApp.Map.Server.Impl do Process.send_after(self(), :update_tracked_characters, 100) Process.send_after(self(), :update_presence, @update_presence_timeout) Process.send_after(self(), :cleanup_connections, 5000) - Process.send_after(self(), :cleanup_systems, 10000) - Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout) + Process.send_after(self(), :cleanup_systems, 10_000) + Process.send_after(self(), :cleanup_characters, :timer.minutes(5)) Process.send_after(self(), :backup_state, @backup_state_timeout) WandererApp.Cache.insert("map_#{map_id}:started", true) @@ -259,37 +259,37 @@ defmodule WandererApp.Map.Server.Impl do state, update ), - do: state |> _update_system(:update_name, [:name], update) + do: state |> update_system(:update_name, [:name], update) def update_system_description( state, update ), - do: state |> _update_system(:update_description, [:description], update) + do: state |> update_system(:update_description, [:description], update) def update_system_status( state, update ), - do: state |> _update_system(:update_status, [:status], update) + do: state |> update_system(:update_status, [:status], update) def update_system_tag( state, update ), - do: state |> _update_system(:update_tag, [:tag], update) + do: state |> update_system(:update_tag, [:tag], update) def update_system_locked( state, update ), - do: state |> _update_system(:update_locked, [:locked], update) + do: state |> update_system(:update_locked, [:locked], update) def update_system_labels( state, update ), - do: state |> _update_system(:update_labels, [:labels], update) + do: state |> update_system(:update_labels, [:labels], update) def update_system_position( %{rtree_name: rtree_name} = state, @@ -297,7 +297,7 @@ defmodule WandererApp.Map.Server.Impl do ), do: state - |> _update_system( + |> update_system( :update_position, [:position_x, :position_y], update, @@ -445,12 +445,34 @@ defmodule WandererApp.Map.Server.Impl do state end + def get_connection_info( + %{map_id: map_id} = _state, + %{ + solar_system_source_id: solar_system_source_id, + solar_system_target_id: solar_system_target_id + } = _connection_info + ) do + WandererApp.Map.find_connection( + map_id, + solar_system_source_id, + solar_system_target_id + ) + |> case do + {:ok, %{id: connection_id}} -> + connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id, nil) + {:ok, %{marl_eol_time: connection_mark_eol_time}} + + _ -> + {:error, :not_found} + end + end + def update_connection_time_status( %{map_id: map_id} = state, connection_update ), do: - _update_connection(state, :update_time_status, [:time_status], connection_update, fn + update_connection(state, :update_time_status, [:time_status], connection_update, fn %{id: connection_id, time_status: time_status} -> case time_status == @connection_time_status_eol do true -> @@ -469,25 +491,25 @@ defmodule WandererApp.Map.Server.Impl do state, connection_update ), - do: _update_connection(state, :update_mass_status, [:mass_status], connection_update) + do: update_connection(state, :update_mass_status, [:mass_status], connection_update) def update_connection_ship_size_type( state, connection_update ), - do: _update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update) + do: update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update) def update_connection_locked( state, connection_update ), - do: _update_connection(state, :update_locked, [:locked], connection_update) + do: update_connection(state, :update_locked, [:locked], connection_update) def update_connection_custom_info( state, connection_update ), - do: _update_connection(state, :update_custom_info, [:custom_info], connection_update) + do: update_connection(state, :update_custom_info, [:custom_info], connection_update) def import_settings(%{map_id: map_id} = state, settings, user_id) do WandererApp.Cache.put( @@ -497,9 +519,9 @@ defmodule WandererApp.Map.Server.Impl do state = state - |> _maybe_import_systems(settings, user_id, nil) - |> _maybe_import_connections(settings, user_id) - |> _maybe_import_hubs(settings, user_id) + |> maybe_import_systems(settings, user_id, nil) + |> maybe_import_connections(settings, user_id) + |> maybe_import_hubs(settings, user_id) WandererApp.Cache.take("map_#{map_id}:importing") @@ -531,7 +553,7 @@ defmodule WandererApp.Map.Server.Impl do update |> case do {:character_location, location_info, old_location_info} -> - _update_location( + update_location( character_id, location_info, old_location_info, @@ -616,14 +638,14 @@ defmodule WandererApp.Map.Server.Impl do def handle_event(:update_presence, %{map_id: map_id} = state) do Process.send_after(self(), :update_presence, @update_presence_timeout) - _update_presence(map_id) + update_presence(map_id) state end def handle_event(:backup_state, state) do Process.send_after(self(), :backup_state, @backup_state_timeout) - {:ok, _map_state} = state |> _save_map_state() + {:ok, _map_state} = state |> save_map_state() state end @@ -950,7 +972,7 @@ defmodule WandererApp.Map.Server.Impl do end def broadcast!(map_id, event, payload \\ nil) do - if _can_broadcast?(map_id) do + if can_broadcast?(map_id) do @pubsub_client.broadcast!(WandererApp.PubSub, map_id, %{event: event, payload: payload}) end @@ -977,23 +999,23 @@ defmodule WandererApp.Map.Server.Impl do end end - defp get_connection_mark_eol_time(map_id, connection_id) do + defp get_connection_mark_eol_time(map_id, connection_id, default \\ DateTime.utc_now()) do WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time") |> case do nil -> - DateTime.utc_now() + default value -> value end end - defp _can_broadcast?(map_id), + defp can_broadcast?(map_id), do: not WandererApp.Cache.lookup!("map_#{map_id}:importing", false) and WandererApp.Cache.lookup!("map_#{map_id}:started", false) - defp _update_location( + defp update_location( character_id, location, old_location, @@ -1176,7 +1198,7 @@ defmodule WandererApp.Map.Server.Impl do end end - defp _update_connection( + defp update_connection( %{map_id: map_id} = state, update_method, attributes, @@ -1192,7 +1214,7 @@ defmodule WandererApp.Map.Server.Impl do solar_system_source_id, solar_system_target_id ), - {:ok, update_map} <- _get_update_map(update, attributes), + {:ok, update_map} <- get_update_map(update, attributes), :ok <- WandererApp.Map.update_connection( map_id, @@ -1218,7 +1240,7 @@ defmodule WandererApp.Map.Server.Impl do end end - defp _update_system( + defp update_system( %{map_id: map_id} = state, update_method, attributes, @@ -1231,7 +1253,7 @@ defmodule WandererApp.Map.Server.Impl do map_id, update.solar_system_id ), - {:ok, update_map} <- _get_update_map(update, attributes) do + {:ok, update_map} <- get_update_map(update, attributes) do {:ok, updated_system} = apply(WandererApp.MapSystemRepo, update_method, [ system, @@ -1242,7 +1264,7 @@ defmodule WandererApp.Map.Server.Impl do callback_fn.(updated_system) end - _update_map_system_last_activity(map_id, updated_system) + update_map_system_last_activity(map_id, updated_system) state else @@ -1252,7 +1274,7 @@ defmodule WandererApp.Map.Server.Impl do end end - defp _get_update_map(update, attributes), + defp get_update_map(update, attributes), do: {:ok, Enum.reduce(attributes, Map.new(), fn attribute, map -> @@ -1358,7 +1380,7 @@ defmodule WandererApp.Map.Server.Impl do state end - defp _save_map_state(%{map_id: map_id} = _state) do + defp save_map_state(%{map_id: map_id} = _state) do systems_last_activity = map_id |> WandererApp.Map.list_systems!() @@ -1489,7 +1511,7 @@ defmodule WandererApp.Map.Server.Impl do state end - def _maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do + def maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do state = systems |> Enum.reduce(state, fn %{ @@ -1533,7 +1555,7 @@ defmodule WandererApp.Map.Server.Impl do |> delete_systems(removed_system_ids, user_id, character_id) end - def _maybe_import_connections(state, %{"connections" => connections} = _settings, _user_id) do + def maybe_import_connections(state, %{"connections" => connections} = _settings, _user_id) do connections |> Enum.reduce(state, fn %{ "source" => source, @@ -1569,7 +1591,7 @@ defmodule WandererApp.Map.Server.Impl do end) end - def _maybe_import_hubs(state, %{"hubs" => hubs} = _settings, _user_id) do + def maybe_import_hubs(state, %{"hubs" => hubs} = _settings, _user_id) do hubs |> Enum.reduce(state, fn hub, acc -> solar_system_id = hub |> String.to_integer() @@ -1579,7 +1601,7 @@ defmodule WandererApp.Map.Server.Impl do end) end - defp _update_map_system_last_activity( + defp update_map_system_last_activity( map_id, updated_system ) do @@ -1673,7 +1695,7 @@ defmodule WandererApp.Map.Server.Impl do end end - defp _update_presence(map_id) do + defp update_presence(map_id) do case WandererApp.Cache.lookup!("map_#{map_id}:started", false) and WandererApp.Cache.get_and_remove!("map_#{map_id}:presence_updated", false) do true -> @@ -1727,15 +1749,15 @@ defmodule WandererApp.Map.Server.Impl do track_characters(rest, map_id) end - defp track_character(character_id, map_id) do - WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ - map_id: map_id, - track: true, - track_online: true, - track_location: true, - track_ship: true - }) - end + defp track_character(character_id, map_id), + do: + WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ + map_id: map_id, + track: true, + track_online: true, + track_location: true, + track_ship: true + }) defp _update_character(map_id, character_id) do {:ok, character} = WandererApp.Character.get_character(character_id) diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex index 864860c6..73bfc1c4 100644 --- a/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex @@ -162,6 +162,16 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do {:noreply, socket} end + def handle_ui_event( + "get_connection_info", + %{"from" => from, "to" => to} = _event, + %{assigns: %{map_id: map_id}} = socket + ) do + {:ok, info} = map_id |> get_connection_info(from, to) + + {:reply, info, socket} + end + def handle_ui_event( "get_passages", %{"from" => from, "to" => to} = _event, @@ -194,4 +204,19 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do {:ok, %{passages: passages}} end + + defp get_connection_info(map_id, from, to) do + map_id + |> WandererApp.Map.Server.get_connection_info(%{ + solar_system_source_id: "#{from}" |> String.to_integer(), + solar_system_target_id: "#{to}" |> String.to_integer() + }) + |> case do + {:ok, info} -> + {:ok, info} + + _ -> + {:ok, %{}} + end + end end diff --git a/lib/wanderer_app_web/live/maps/map_event_handler.ex b/lib/wanderer_app_web/live/maps/map_event_handler.ex index 88d7fc08..4363dcae 100644 --- a/lib/wanderer_app_web/live/maps/map_event_handler.ex +++ b/lib/wanderer_app_web/live/maps/map_event_handler.ex @@ -61,6 +61,7 @@ defmodule WandererAppWeb.MapEventHandler do @map_connection_ui_events [ "manual_add_connection", "manual_delete_connection", + "get_connection_info", "get_passages", "update_connection_time_status", "update_connection_mass_status", diff --git a/priv/repo/migrations/20240922090429_install_ash-functions_extension_4.exs b/priv/repo/migrations/20240922090429_install_ash-functions_extension_4.exs deleted file mode 100644 index a84cbb9b..00000000 --- a/priv/repo/migrations/20240922090429_install_ash-functions_extension_4.exs +++ /dev/null @@ -1,54 +0,0 @@ -defmodule WandererApp.Repo.Migrations.InstallAshFunctionsExtension420240922090427 do - @moduledoc """ - Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback - - This file was autogenerated with `mix ash_postgres.generate_migrations` - """ - - use Ecto.Migration - - def up do - execute(""" - CREATE OR REPLACE FUNCTION uuid_generate_v7() - RETURNS UUID - AS $$ - DECLARE - timestamp TIMESTAMPTZ; - microseconds INT; - BEGIN - timestamp = clock_timestamp(); - microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; - - RETURN encode( - set_byte( - set_byte( - overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 - ), - 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int - ), - 7, microseconds::bit(8)::int - ), - 'hex')::UUID; - END - $$ - LANGUAGE PLPGSQL - VOLATILE; - """) - - execute(""" - CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) - RETURNS TIMESTAMP WITHOUT TIME ZONE - AS $$ - SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); - $$ - LANGUAGE SQL - IMMUTABLE PARALLEL SAFE STRICT; - """) - end - - def down do - # Uncomment this if you actually want to uninstall the extensions - # when this migration is rolled back: - execute("DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)") - end -end