Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,28 @@ import { useTitle } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { THEME_MODES, useSettingsStore } from '@/shared/stores'
import { BadgeNumber, IconSvg } from '@/shared/ui'
import { BadgeNumber, IconSvg, SelectControl } from '@/shared/ui'

const settingsStore = useSettingsStore()
const { changeTheme, changeNavbar, changeEventCountsVisibility, changeActiveCodeEditor } =
settingsStore
const { themeType, isFixedHeader, isVisibleEventCounts, codeEditor } = storeToRefs(settingsStore)
const {
changeTheme,
changeNavbar,
changeEventCountsVisibility,
setAutoDeleteEventsTime,
changeActiveCodeEditor
} = settingsStore
const { themeType, isFixedHeader, isVisibleEventCounts, autoDeleteEventsTime, codeEditor } =
storeToRefs(settingsStore)

const isDarkMode = computed(() => themeType.value === THEME_MODES.DARK)

const deleteEventsAfter = computed<string>({
get: () => (autoDeleteEventsTime.value === 'none' ? 'none' : String(autoDeleteEventsTime.value)),
set: (val) => {
setAutoDeleteEventsTime(val)
}
})

// TODO: add throttle
const changeCodeEditor = (event: Event) => {
const editor = (event.target as HTMLInputElement).value
Expand Down Expand Up @@ -114,6 +127,21 @@ useTitle('Settings | Buggregator')
</div>
</div>

<div class="settings-page-content__title">
Delete Events After:
</div>

<SelectControl
v-model="deleteEventsAfter"
name="delete_events_after"
:options="[
{ value: 'none', text: 'No' },
{ value: '1', text: '1 min' },
{ value: '5', text: '5 min' },
{ value: '10', text: '10 min' }
]"
/>

<div class="settings-page-content__title">
Code Editor Open Link:
</div>
Expand Down
68 changes: 67 additions & 1 deletion src/shared/lib/io/use-events-requests.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import {storeToRefs} from "pinia";
import {useEventsStore, useProfileStore} from "../../stores";
import type { EventId, EventType, ServerEvent } from '../../types';
import type { EventId, EventType, EventTypeCount, EventsPreviewMeta, ServerEvent } from '../../types';
import { REST_API_URL } from "./constants";

type TUseEventsRequests = () => {
getAll: () => Promise<ServerEvent<unknown>[]>,
getPreviewPageByType: (
type: EventType,
limit: number,
cursor?: string | null
) => Promise<{ data: ServerEvent<unknown>[]; meta: EventsPreviewMeta | null }>,
getTypeCounts: () => Promise<EventTypeCount[]>,
getSingle: (id: EventId) => Promise<ServerEvent<EventType> | null>,
deleteAll: () => Promise<void | Response>,
deleteList: (uuids: EventId[]) => Promise<void | Response>,
Expand All @@ -22,6 +28,25 @@ export const useEventsRequests: TUseEventsRequests = () => {
const headers = {"X-Auth-Token": token.value }
const getEventRestUrl = (param: string): string => `${REST_API_URL}/api/event/${param}${project.value ? `?project=${project.value}` : ''}`
const getEventsPreviewRestUrl = (): string => `${REST_API_URL}/api/events/preview${project.value ? `?project=${project.value}` : ''}`
const getEventsPreviewByTypeRestUrl = (
type: EventType,
limit: number,
cursor?: string | null
): string => {
const params = new URLSearchParams({
type,
limit: String(limit),
...(project.value ? { project: project.value } : {}),
})

if (cursor) {
params.set('cursor', cursor)
}

return `${REST_API_URL}/api/events/preview?${params.toString()}`
}
const getEventsTypeCountsRestUrl = (): string =>
`${REST_API_URL}/api/events/type-counts${project.value ? `?project=${project.value}` : ''}`

const getAll = () => fetch(getEventsPreviewRestUrl(), { headers })
.then((response) => response.json())
Expand All @@ -41,6 +66,45 @@ export const useEventsRequests: TUseEventsRequests = () => {
})
.then((events: ServerEvent<unknown>[]) => events)

const getPreviewPageByType = (type: EventType, limit: number, cursor?: string | null) =>
fetch(getEventsPreviewByTypeRestUrl(type, limit, cursor), { headers })
.then((response) => response.json())
.then((response) => {
if (response?.data) {
return {
data: response.data as ServerEvent<unknown>[],
meta: (response.meta ?? null) as EventsPreviewMeta | null,
}
}

if (response?.code === 403) {
console.error('Forbidden')
return { data: [], meta: null }
}

console.error('Fetch Error')

return { data: [], meta: null }
})

const getTypeCounts = () => fetch(getEventsTypeCountsRestUrl(), { headers })
.then((response) => response.json())
.then((response) => {
if (response?.data) {
return response.data as EventTypeCount[]
}

if (response?.code === 403) {
console.error('Forbidden')
return [];
}

console.error('Fetch Error')

return [];
})
.then((counts: EventTypeCount[]) => counts)

const getSingle = (id: EventId) => fetch(getEventRestUrl(id), {headers})
.then((response) => response.json())
.then((response) => {
Expand Down Expand Up @@ -91,6 +155,8 @@ export const useEventsRequests: TUseEventsRequests = () => {

return {
getAll,
getPreviewPageByType,
getTypeCounts,
getSingle,
deleteAll,
deleteList,
Expand Down
4 changes: 4 additions & 0 deletions src/shared/lib/use-api-transport/use-api-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const useApiTransport = () => {
const connectionStore = useConnectionStore()
const {
getAll,
getPreviewPageByType,
getTypeCounts,
getSingle,
deleteAll,
deleteList,
Expand Down Expand Up @@ -137,6 +139,8 @@ export const useApiTransport = () => {

return {
getEventsAll: getAll,
getEventsPreviewByTypePage: getPreviewPageByType,
getEventsTypeCounts: getTypeCounts,
getEvent: getSingle,
deleteEvent,
deleteEventsAll,
Expand Down
116 changes: 116 additions & 0 deletions src/shared/lib/use-events/auto-delete-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { storeToRefs } from "pinia";
import { watch } from "vue";
import { useEventsStore, useSettingsStore } from "../../stores";
import type { EventId } from "../../types";
import { useEventsApi } from "./use-events-api";

type AutoDeleteTime = number | "none";

const autoDeleteTimers = new Map<EventId, ReturnType<typeof setTimeout>>();
let autoDeleteInitialized = false;

const toAutoDeleteMs = (val: AutoDeleteTime): number | null => {
if (val === "none") return null;
return val * 60 * 1000;
};

const clearAutoDeleteTimer = (eventId: EventId) => {
const timer = autoDeleteTimers.get(eventId);
if (!timer) return;
globalThis.clearTimeout(timer);
autoDeleteTimers.delete(eventId);
};

const clearAllAutoDeleteTimers = () => {
autoDeleteTimers.forEach((timer) => globalThis.clearTimeout(timer));
autoDeleteTimers.clear();
};

export const ensureAutoDeleteWatcher = () => {
if (autoDeleteInitialized) return;
autoDeleteInitialized = true;

const eventsStore = useEventsStore();
const settingsStore = useSettingsStore();
const eventsApi = useEventsApi();

const { events, lockedIds } = storeToRefs(eventsStore);
const { autoDeleteEventsTime } = storeToRefs(settingsStore);

const schedule = (eventId: EventId) => {
const ms = toAutoDeleteMs(autoDeleteEventsTime.value);
if (ms === null || autoDeleteTimers.has(eventId)) return;

const timer = globalThis.setTimeout(() => {
autoDeleteTimers.delete(eventId);

const locked = lockedIds.value ?? [];
if (locked.includes(eventId)) return;

void eventsApi.removeById(eventId);
}, ms);

autoDeleteTimers.set(eventId, timer);
};

const rescheduleAll = () => {
clearAllAutoDeleteTimers();

const ms = toAutoDeleteMs(autoDeleteEventsTime.value);
if (ms === null) return;

events.value.forEach(({ uuid }) => {
schedule(uuid);
});
};

const syncTimers = (currentIds: Set<EventId>, prevIds: Set<EventId>) => {
prevIds.forEach((id) => {
if (!currentIds.has(id)) {
clearAutoDeleteTimer(id);
}
});

if (toAutoDeleteMs(autoDeleteEventsTime.value) === null) return;

currentIds.forEach((id) => {
if (!prevIds.has(id)) {
schedule(id);
}
});
};

watch(
autoDeleteEventsTime,
() => {
rescheduleAll();
},
{ immediate: true },
);

watch(
() => events.value.map(({ uuid }) => uuid),
(current, prev) => {
const currentIds = new Set(current);
const prevIds = new Set(prev ?? []);

syncTimers(currentIds, prevIds);
},
);

watch(
() => lockedIds.value.slice(),
(current, prev) => {
const currentIds = new Set(current);
const prevIds = new Set(prev ?? []);

const eventsIds = new Set(events.value.map(({ uuid }) => uuid));
const unlockedIds = new Set(
[...prevIds].filter((id) => !currentIds.has(id) && eventsIds.has(id)),
);

syncTimers(currentIds, prevIds);
syncTimers(unlockedIds, new Set<EventId>());
},
);
};
70 changes: 59 additions & 11 deletions src/shared/lib/use-events/use-events-api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { storeToRefs } from "pinia";
import type { Ref } from 'vue';
import { useEventsStore } from "../../stores";
import type { EventId, EventType, ServerEvent } from '../../types';
import { EventTypes, type EventId, type EventType, type EventsPreviewMeta, type ServerEvent } from '../../types';
import { useApiTransport } from '../use-api-transport'


export type TUseEventsApi = {
items: Ref<ServerEvent<unknown>[]>
getItem: (id: EventId) => Promise<ServerEvent<EventType> | null>
getAll: () => void
loadMoreByType: (
type: EventType,
cursor?: string | null
) => Promise<{ meta: EventsPreviewMeta | null; data: ServerEvent<unknown>[] }>
removeAll: () => void
removeByType: (type: EventType) => void
removeById: (id: EventId) => void
Expand All @@ -26,10 +30,13 @@ export const useEventsApi = (): TUseEventsApi => {
deleteEventsAll,
deleteEventsList,
deleteEventsByType,
getEventsAll,
getEventsPreviewByTypePage,
getEventsTypeCounts,
getEvent,
} = useApiTransport();

const LOADING_PAGE_SIZE = 25;

const removeList = async (uuids: EventId[]) => {
const res = await deleteEventsList(uuids)

Expand Down Expand Up @@ -79,22 +86,63 @@ export const useEventsApi = (): TUseEventsApi => {
}

const getAll = () => {
getEventsAll().then((eventsList: ServerEvent<unknown>[]) => {
if (eventsList.length) {
eventsStore.initializeEvents(eventsList);
} else {
// NOTE: clear cached events hardly
eventsStore.removeAll();
}
}).catch((e) => {
console.error(e);
eventsStore.initializeEvents([]);
eventsStore.resetPreviewPagination();

getEventsTypeCounts()
.then((typeCounts) => {
eventsStore.setEventCounts(typeCounts);
})
.catch((e) => {
console.error(e);
eventsStore.resetEventCounts();
});

Object.values(EventTypes).forEach((eventType) => {
getEventsPreviewByTypePage(eventType, LOADING_PAGE_SIZE)
.then(({ data, meta }) => {
if (data.length) {
eventsStore.mergeEvents(data, { updateCounts: false });
}
eventsStore.setPreviewPagination(eventType, meta);
})
.catch((e) => {
console.error(e);
})
})
}

const loadMoreByType = async (eventType: EventType, cursor?: string | null) => {
const pagination = eventsStore.previewPagination[eventType as EventTypes];
if (!pagination?.hasMore) {
return { meta: null, data: [] };
}

const requestCursor = cursor ?? pagination.cursor;
const { data, meta } = await getEventsPreviewByTypePage(
eventType,
LOADING_PAGE_SIZE,
requestCursor,
);

if (data.length) {
eventsStore.mergeEvents(data, { updateCounts: false });

if (eventsStore.cachedIds[eventType]?.length) {
eventsStore.appendCachedIds(eventType, data.map(({ uuid }) => uuid));
}
}

eventsStore.setPreviewPagination(eventType, meta);

return { meta, data };
}

return {
items: events as unknown as Ref<ServerEvent<unknown>[]>,
getItem: getEvent,
getAll,
loadMoreByType,
removeAll,
removeByType,
removeById,
Expand Down
Loading
Loading