chore: release version v1.44.0

This commit is contained in:
Dmitry Popov
2025-02-02 22:24:47 +01:00
parent 5bd968acae
commit 304f4b01ab
12 changed files with 151 additions and 76 deletions

View File

@@ -37,6 +37,7 @@ const INITIAL_DATA: MapData = {
userPermissions: {}, userPermissions: {},
systemSignatures: {} as Record<string, SystemSignature[]>, systemSignatures: {} as Record<string, SystemSignature[]>,
options: {} as Record<string, string | boolean>, options: {} as Record<string, string | boolean>,
is_subscription_active: false,
}; };
export interface MapContextProps { export interface MapContextProps {

View File

@@ -103,13 +103,3 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
label: 'Kills', label: 'Kills',
}, },
]; ];
export function getWidgetsCheckboxesProps(detailedKillsDisabled: boolean): WidgetsCheckboxesType {
return filterOutKills(WIDGETS_CHECKBOXES_PROPS, detailedKillsDisabled);
}
function filterOutKills<T extends { id: WidgetsIds }>(items: T[], shouldFilter: boolean) {
if (!shouldFilter) return items;
return items.filter((w) => w.id !== WidgetsIds.kills);
}

View File

@@ -9,7 +9,7 @@ import { KillsSettingsDialog } from './components/SystemKillsSettingsDialog';
export const SystemKills: React.FC = () => { export const SystemKills: React.FC = () => {
const { const {
data: { selectedSystems, systems }, data: { selectedSystems, systems, is_subscription_active: isSubscriptionActive },
outCommand, outCommand,
} = useMapRootState(); } = useMapRootState();
@@ -41,6 +41,13 @@ export const SystemKills: React.FC = () => {
<div className="h-full flex flex-col min-h-0"> <div className="h-full flex flex-col min-h-0">
<div className="flex flex-col flex-1 min-h-0"> <div className="flex flex-col flex-1 min-h-0">
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}> <Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
{!isSubscriptionActive && (
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
Kills available with &#39;Active&#39; map subscription only (contact map administrators)
</div>
)}
{isSubscriptionActive && (
<>
{isNothingSelected && ( {isNothingSelected && (
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm"> <div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
No system selected (or toggle Show all systems) No system selected (or toggle Show all systems)
@@ -76,6 +83,8 @@ export const SystemKills: React.FC = () => {
/> />
</div> </div>
)} )}
</>
)}
</Widget> </Widget>
</div> </div>

View File

@@ -1,5 +1,5 @@
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components'; import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { getWidgetsCheckboxesProps, WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx'; import { WIDGETS_CHECKBOXES_PROPS, WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback } from 'react'; import { useCallback } from 'react';
@@ -9,20 +9,17 @@ export interface WidgetsSettingsProps {}
// eslint-disable-next-line no-empty-pattern // eslint-disable-next-line no-empty-pattern
export const WidgetsSettings = ({}: WidgetsSettingsProps) => { export const WidgetsSettings = ({}: WidgetsSettingsProps) => {
const { windowsSettings, toggleWidgetVisibility, resetWidgets, data } = useMapRootState(); const { windowsSettings, toggleWidgetVisibility, resetWidgets } = useMapRootState();
const handleWidgetSettingsChange = useCallback( const handleWidgetSettingsChange = useCallback(
(widget: WidgetsIds) => toggleWidgetVisibility(widget), (widget: WidgetsIds) => toggleWidgetVisibility(widget),
[toggleWidgetVisibility], [toggleWidgetVisibility],
); );
const detailedKillsDisabled = data.options?.detailedKillsDisabled === true;
const widgetProps = getWidgetsCheckboxesProps(detailedKillsDisabled);
return ( return (
<div className="flex flex-col h-full gap-2"> <div className="flex flex-col h-full gap-2">
<div> <div>
{widgetProps.map(widget => ( {WIDGETS_CHECKBOXES_PROPS.map(widget => (
<PrettySwitchbox <PrettySwitchbox
key={widget.id} key={widget.id}
label={widget.label} label={widget.label}

View File

@@ -22,4 +22,5 @@ export type MapUnionTypes = {
connections: SolarSystemConnection[]; connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>; userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>; options: Record<string, string | boolean>;
is_subscription_active: boolean;
}; };

View File

@@ -70,9 +70,14 @@ defmodule WandererApp.Map do
def get_characters_limit(map_id), def get_characters_limit(map_id),
do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 50)} do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 50)}
def is_subscription_active?(map_id) do def is_subscription_active?(map_id),
do: is_subscription_active?(map_id, WandererApp.Env.map_subscriptions_enabled?())
def is_subscription_active?(_map_id, false), do: {:ok, true}
def is_subscription_active?(map_id, _map_subscriptions_enabled) do
{:ok, %{plan: plan}} = WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id) {:ok, %{plan: plan}} = WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id)
not WandererApp.Env.map_subscriptions_enabled?() || plan != :alpha {:ok, plan != :alpha}
end end
def get_options(map_id), def get_options(map_id),

View File

@@ -28,6 +28,8 @@ defmodule WandererApp.Map.ZkbDataFetcher do
@impl true @impl true
def handle_info(:fetch_data, %{iteration: iteration} = state) do def handle_info(:fetch_data, %{iteration: iteration} = state) do
zkill_preload_disabled = WandererApp.Env.zkill_preload_disabled?()
WandererApp.Map.RegistryHelper.list_all_maps() WandererApp.Map.RegistryHelper.list_all_maps()
|> Task.async_stream( |> Task.async_stream(
fn %{id: map_id, pid: _server_pid} -> fn %{id: map_id, pid: _server_pid} ->
@@ -35,7 +37,11 @@ defmodule WandererApp.Map.ZkbDataFetcher do
if WandererApp.Map.Server.map_pid(map_id) do if WandererApp.Map.Server.map_pid(map_id) do
update_map_kills(map_id) update_map_kills(map_id)
unless WandererApp.Env.zkill_preload_disabled?() do {:ok, is_subscription_active} = map_id |> WandererApp.Map.is_subscription_active?()
can_preload_zkill = not zkill_preload_disabled && is_subscription_active
if can_preload_zkill do
update_detailed_map_kills(map_id) update_detailed_map_kills(map_id)
end end
end end
@@ -52,7 +58,7 @@ defmodule WandererApp.Map.ZkbDataFetcher do
new_iteration = iteration + 1 new_iteration = iteration + 1
cond do cond do
WandererApp.Env.zkill_preload_disabled?() -> zkill_preload_disabled ->
# If preload is disabled, just update iteration # If preload is disabled, just update iteration
{:noreply, %{state | iteration: new_iteration}} {:noreply, %{state | iteration: new_iteration}}
@@ -138,11 +144,11 @@ defmodule WandererApp.Map.ZkbDataFetcher do
end) end)
WandererApp.Cache.put("map_#{map_id}:zkb_ids", updated_ids_map, WandererApp.Cache.put("map_#{map_id}:zkb_ids", updated_ids_map,
ttl: :timer.hours(KillsCache.killmail_ttl) ttl: :timer.hours(KillsCache.killmail_ttl())
) )
WandererApp.Cache.put("map_#{map_id}:zkb_detailed_kills", updated_details_map, WandererApp.Cache.put("map_#{map_id}:zkb_detailed_kills", updated_details_map,
ttl: :timer.hours(KillsCache.killmail_ttl) ttl: :timer.hours(KillsCache.killmail_ttl())
) )
changed_data = Map.take(updated_details_map, changed_systems) changed_data = Map.take(updated_details_map, changed_systems)

View File

@@ -65,15 +65,18 @@ defmodule WandererApp.Zkb.KillsPreloader do
last_active_maps_result = WandererApp.Api.MapState.get_last_active(cutoff_time) last_active_maps_result = WandererApp.Api.MapState.get_last_active(cutoff_time)
last_active_maps = resolve_last_active_maps(last_active_maps_result) last_active_maps = resolve_last_active_maps(last_active_maps_result)
active_maps_with_subscription = get_active_maps_with_subscription(last_active_maps)
# Gather systems from those maps # Gather systems from those maps
system_tuples = gather_visible_systems(last_active_maps) system_tuples = gather_visible_systems(active_maps_with_subscription)
unique_systems = Enum.uniq(system_tuples) unique_systems = Enum.uniq(system_tuples)
Logger.debug(""" Logger.debug(fn ->
"""
[KillsPreloader] Found #{length(unique_systems)} unique systems \ [KillsPreloader] Found #{length(unique_systems)} unique systems \
across #{length(last_active_maps)} map(s) across #{length(active_maps_with_subscription)} map(s)
""") """
end)
# ---- QUICK PASS ---- # ---- QUICK PASS ----
state_quick = %{state | phase: :quick_pass} state_quick = %{state | phase: :quick_pass}
@@ -83,7 +86,9 @@ defmodule WandererApp.Zkb.KillsPreloader do
do_pass(unique_systems, :quick, @quick_hours, @quick_limit, state_quick) do_pass(unique_systems, :quick, @quick_hours, @quick_limit, state_quick)
end) end)
Logger.info("[KillsPreloader] Phase 1 (quick) done => calls_count=#{state_after_quick.calls_count}, elapsed=#{time_quick_ms}ms") Logger.info(
"[KillsPreloader] Phase 1 (quick) done => calls_count=#{state_after_quick.calls_count}, elapsed=#{time_quick_ms}ms"
)
# ---- EXPANDED PASS ---- # ---- EXPANDED PASS ----
state_expanded = %{state_after_quick | phase: :expanded_pass} state_expanded = %{state_after_quick | phase: :expanded_pass}
@@ -93,7 +98,9 @@ defmodule WandererApp.Zkb.KillsPreloader do
do_pass(unique_systems, :expanded, @quick_hours, @expanded_limit, state_expanded) do_pass(unique_systems, :expanded, @quick_hours, @expanded_limit, state_expanded)
end) end)
Logger.info("[KillsPreloader] Phase 2 (expanded) done => calls_count=#{final_state.calls_count}, elapsed=#{time_expanded_ms}ms") Logger.info(
"[KillsPreloader] Phase 2 (expanded) done => calls_count=#{final_state.calls_count}, elapsed=#{time_expanded_ms}ms"
)
# Reset phase to :idle # Reset phase to :idle
{:noreply, %{final_state | phase: :idle}} {:noreply, %{final_state | phase: :idle}}
@@ -125,6 +132,13 @@ defmodule WandererApp.Zkb.KillsPreloader do
[] []
end end
defp get_active_maps_with_subscription(maps) do
maps
|> Enum.filter(fn map ->
{:ok, is_subscription_active} = map.id |> WandererApp.Map.is_subscription_active?()
is_subscription_active
end)
end
defp gather_visible_systems(maps) do defp gather_visible_systems(maps) do
maps maps
@@ -136,15 +150,19 @@ defmodule WandererApp.Zkb.KillsPreloader do
Enum.map(systems, fn sys -> {the_map_id, sys.solar_system_id} end) Enum.map(systems, fn sys -> {the_map_id, sys.solar_system_id} end)
{:error, reason} -> {:error, reason} ->
Logger.warning("[KillsPreloader] get_visible_by_map failed => map_id=#{inspect(the_map_id)}, reason=#{inspect(reason)}") Logger.warning(
"[KillsPreloader] get_visible_by_map failed => map_id=#{inspect(the_map_id)}, reason=#{inspect(reason)}"
)
[] []
end end
end) end)
end end
defp do_pass(unique_systems, pass_type, hours, limit, state) do defp do_pass(unique_systems, pass_type, hours, limit, state) do
Logger.info("[KillsPreloader] Starting #{pass_type} pass => #{length(unique_systems)} systems") Logger.info(
"[KillsPreloader] Starting #{pass_type} pass => #{length(unique_systems)} systems"
)
{final_state, kills_map} = {final_state, kills_map} =
unique_systems unique_systems
@@ -167,29 +185,45 @@ defmodule WandererApp.Zkb.KillsPreloader do
end end
defp fetch_kills_for_system(system_id, :quick, hours, limit, state) do defp fetch_kills_for_system(system_id, :quick, hours, limit, state) do
Logger.debug("[KillsPreloader] Quick fetch => system=#{system_id}, hours=#{hours}, limit=#{limit}") Logger.debug(fn ->
"[KillsPreloader] Quick fetch => system=#{system_id}, hours=#{hours}, limit=#{limit}"
end)
case KillsProvider.Fetcher.fetch_kills_for_system(system_id, hours, state, limit: limit, force: false) do case KillsProvider.Fetcher.fetch_kills_for_system(system_id, hours, state,
limit: limit,
force: false
) do
{:ok, kills, updated_state} -> {:ok, kills, updated_state} ->
{:ok, system_id, kills, updated_state} {:ok, system_id, kills, updated_state}
{:error, reason, updated_state} -> {:error, reason, updated_state} ->
Logger.warning("[KillsPreloader] Quick fetch failed => system=#{system_id}, reason=#{inspect(reason)}") Logger.warning(
"[KillsPreloader] Quick fetch failed => system=#{system_id}, reason=#{inspect(reason)}"
)
{:error, reason, updated_state} {:error, reason, updated_state}
end end
end end
defp fetch_kills_for_system(system_id, :expanded, hours, limit, state) do defp fetch_kills_for_system(system_id, :expanded, hours, limit, state) do
Logger.debug("[KillsPreloader] Expanded fetch => system=#{system_id}, hours=#{hours}, limit=#{limit} (forcing refresh)") Logger.debug(fn ->
"[KillsPreloader] Expanded fetch => system=#{system_id}, hours=#{hours}, limit=#{limit} (forcing refresh)"
end)
with {:ok, kills_1h, updated_state} <- with {:ok, kills_1h, updated_state} <-
KillsProvider.Fetcher.fetch_kills_for_system(system_id, hours, state, limit: limit, force: true), KillsProvider.Fetcher.fetch_kills_for_system(system_id, hours, state,
limit: limit,
force: true
),
{:ok, final_kills, final_state} <- {:ok, final_kills, final_state} <-
maybe_fetch_more_if_needed(system_id, kills_1h, limit, updated_state) do maybe_fetch_more_if_needed(system_id, kills_1h, limit, updated_state) do
{:ok, system_id, final_kills, final_state} {:ok, system_id, final_kills, final_state}
else else
{:error, reason, updated_state} -> {:error, reason, updated_state} ->
Logger.warning("[KillsPreloader] Expanded fetch (#{hours}h) failed => system=#{system_id}, reason=#{inspect(reason)}") Logger.warning(
"[KillsPreloader] Expanded fetch (#{hours}h) failed => system=#{system_id}, reason=#{inspect(reason)}"
)
{:error, reason, updated_state} {:error, reason, updated_state}
end end
end end
@@ -198,9 +232,15 @@ defmodule WandererApp.Zkb.KillsPreloader do
defp maybe_fetch_more_if_needed(system_id, kills_1h, limit, state) do defp maybe_fetch_more_if_needed(system_id, kills_1h, limit, state) do
if length(kills_1h) < limit do if length(kills_1h) < limit do
needed = limit - length(kills_1h) needed = limit - length(kills_1h)
Logger.debug("[KillsPreloader] Expanding to #{@expanded_hours}h => system=#{system_id}, need=#{needed} more kills")
case KillsProvider.Fetcher.fetch_kills_for_system(system_id, @expanded_hours, state, limit: needed, force: true) do Logger.debug(fn ->
"[KillsPreloader] Expanding to #{@expanded_hours}h => system=#{system_id}, need=#{needed} more kills"
end)
case KillsProvider.Fetcher.fetch_kills_for_system(system_id, @expanded_hours, state,
limit: needed,
force: true
) do
{:ok, _kills_24h, updated_state2} -> {:ok, _kills_24h, updated_state2} ->
final_kills = final_kills =
KillsCache.fetch_cached_kills(system_id) KillsCache.fetch_cached_kills(system_id)
@@ -209,7 +249,10 @@ defmodule WandererApp.Zkb.KillsPreloader do
{:ok, final_kills, updated_state2} {:ok, final_kills, updated_state2}
{:error, reason2, updated_state2} -> {:error, reason2, updated_state2} ->
Logger.warning("[KillsPreloader] #{@expanded_hours}h fetch failed => system=#{system_id}, reason=#{inspect(reason2)}") Logger.warning(
"[KillsPreloader] #{@expanded_hours}h fetch failed => system=#{system_id}, reason=#{inspect(reason2)}"
)
{:error, reason2, updated_state2} {:error, reason2, updated_state2}
end end
else else
@@ -243,7 +286,9 @@ defmodule WandererApp.Zkb.KillsPreloader do
do: Logger.error("[KillsPreloader] Expanded fetch task failed => #{inspect(reason)}") do: Logger.error("[KillsPreloader] Expanded fetch task failed => #{inspect(reason)}")
defp broadcast_all_kills(kills_map, pass_type) do defp broadcast_all_kills(kills_map, pass_type) do
Logger.info("[KillsPreloader] Broadcasting kills => #{map_size(kills_map)} systems (#{pass_type})") Logger.info(
"[KillsPreloader] Broadcasting kills => #{map_size(kills_map)} systems (#{pass_type})"
)
Phoenix.PubSub.broadcast!( Phoenix.PubSub.broadcast!(
WandererApp.PubSub, WandererApp.PubSub,

View File

@@ -40,7 +40,7 @@ defmodule WandererApp.Zkb.KillsProvider.ZkbApi do
defp do_req_get(system_id, page) do defp do_req_get(system_id, page) do
url = "#{@zkillboard_api}/kills/systemID/#{system_id}/page/#{page}/" url = "#{@zkillboard_api}/kills/systemID/#{system_id}/page/#{page}/"
Logger.debug("[ZkbApi] GET => system=#{system_id}, page=#{page}, url=#{url}") Logger.debug(fn -> "[ZkbApi] GET => system=#{system_id}, page=#{page}, url=#{url}" end)
try do try do
resp = Req.get!(url, decode_body: :json) resp = Req.get!(url, decode_body: :json)
@@ -56,6 +56,7 @@ defmodule WandererApp.Zkb.KillsProvider.ZkbApi do
[ZkbApi] do_req_get => exception: #{Exception.message(e)} [ZkbApi] do_req_get => exception: #{Exception.message(e)}
#{Exception.format_stacktrace(__STACKTRACE__)} #{Exception.format_stacktrace(__STACKTRACE__)}
""") """)
{:error, :exception} {:error, :exception}
end end
end end
@@ -72,7 +73,7 @@ defmodule WandererApp.Zkb.KillsProvider.ZkbApi do
:ok :ok
{:error, limit} -> {:error, limit} ->
Logger.debug("[ZkbApi] RATE_LIMIT => limit=#{inspect(limit)}") Logger.debug(fn -> "[ZkbApi] RATE_LIMIT => limit=#{inspect(limit)}" end)
{:error, :rate_limited} {:error, :rate_limited}
end end
end end

View File

@@ -468,6 +468,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket socket
|> assign( |> assign(
map_loaded?: true, map_loaded?: true,
is_subscription_active?: Map.get(initial_data, :is_subscription_active, false),
user_characters: user_character_eve_ids, user_characters: user_character_eve_ids,
has_tracked_characters?: has_tracked_characters? has_tracked_characters?: has_tracked_characters?
) )
@@ -530,6 +531,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
{:ok, connections} = map_id |> WandererApp.Map.list_connections() {:ok, connections} = map_id |> WandererApp.Map.list_connections()
{:ok, systems} = map_id |> WandererApp.Map.list_systems() {:ok, systems} = map_id |> WandererApp.Map.list_systems()
{:ok, options} = map_id |> WandererApp.Map.get_options() {:ok, options} = map_id |> WandererApp.Map.get_options()
{:ok, is_subscription_active} = map_id |> WandererApp.Map.is_subscription_active?()
%{ %{
systems: systems:
@@ -537,7 +539,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end), |> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end),
hubs: hubs, hubs: hubs,
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1), connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1),
options: options options: options,
is_subscription_active: is_subscription_active
} }
end end

View File

@@ -34,13 +34,15 @@ defmodule WandererAppWeb.MapAuditLive do
case user_permissions.delete_map do case user_permissions.delete_map do
true -> true ->
{:ok, is_subscription_active} = map_id |> WandererApp.Map.is_subscription_active?()
{:ok, {:ok,
socket socket
|> assign( |> assign(
map_id: map_id, map_id: map_id,
map_name: map_name, map_name: map_name,
map_slug: map_slug, map_slug: map_slug,
map_subscription_active: WandererApp.Map.is_subscription_active?(map_id), map_subscription_active: is_subscription_active,
activity: activity, activity: activity,
can_undo_types: [:systems_removed], can_undo_types: [:systems_removed],
period: period || "1H", period: period || "1H",

View File

@@ -155,7 +155,14 @@ defmodule WandererAppWeb.MapEventHandler do
when event_name in @map_signatures_events, when event_name in @map_signatures_events,
do: MapSignaturesEventHandler.handle_server_event(event, socket) do: MapSignaturesEventHandler.handle_server_event(event, socket)
def handle_event(socket, %{event: event_name} = event) def handle_event(
%{
assigns: %{
is_subscription_active?: true
}
} = socket,
%{event: event_name} = event
)
when event_name in @map_kills_events, when event_name in @map_kills_events,
do: MapKillsEventHandler.handle_server_event(event, socket) do: MapKillsEventHandler.handle_server_event(event, socket)
@@ -212,7 +219,15 @@ defmodule WandererAppWeb.MapEventHandler do
when event in @map_activity_ui_events, when event in @map_activity_ui_events,
do: MapActivityEventHandler.handle_ui_event(event, body, socket) do: MapActivityEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket) def handle_ui_event(
event,
body,
%{
assigns: %{
is_subscription_active?: true
}
} = socket
)
when event in @map_kills_ui_events, when event in @map_kills_ui_events,
do: MapKillsEventHandler.handle_ui_event(event, body, socket) do: MapKillsEventHandler.handle_ui_event(event, body, socket)