Skip to content
Open
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
26 changes: 15 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/background.index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DebugTab } from './shared/db/types';
import { WAKE_UP_BACKGROUND } from './shared/messages';

import Port = chrome.runtime.Port;
import { manageLocalStoreSpace } from './background/services/data';

interface Service {
name: string;
Expand All @@ -30,6 +31,10 @@ const ASYNC_POLL_INTERVAL_MINUTES = 1;
const ASYNC_STATS_INTERVAL_ALARM_NAME = 'send-stats';
const ASYNC_STATS_INTERVAL_MINUTES = 1;

const ASYNC_CLEAN_TIMELINE_ALARM_NAME = 'clean-timeline';
const ASYNC_CLEAN_TIMELINE_MINUTES = 1440; // 24 hours * 60 minutes - one day
const DEFAULT_CUTOFF_DAYS = 60;

function findDebuggingTabIndexFromId(tabIdOrUrl: number | string | undefined) {
if (tabIdOrUrl !== undefined) {
for (let i = 0; i < debuggingTabs.length; i++) {
Expand Down Expand Up @@ -78,6 +83,10 @@ const asyncPollAlarmHandler = async (): Promise<void> => {
const sendStatsAlarmHandler = async (): Promise<void> =>
await sendWebActivityAutomatically();

const cleanTimeLineAlarmHandler = async (): Promise<void> => {
await manageLocalStoreSpace(DEFAULT_CUTOFF_DAYS)
}

const ChromeServiceDefinition: Array<Service> = [
{
handler: asyncPollAlarmHandler,
Expand All @@ -89,6 +98,11 @@ const ChromeServiceDefinition: Array<Service> = [
intervalInMinutes: ASYNC_STATS_INTERVAL_MINUTES,
name: ASYNC_STATS_INTERVAL_ALARM_NAME,
},
{
handler: cleanTimeLineAlarmHandler,
intervalInMinutes: ASYNC_CLEAN_TIMELINE_MINUTES,
name: ASYNC_CLEAN_TIMELINE_ALARM_NAME
}
];

ChromeServiceDefinition.forEach((service) => {
Expand Down
69 changes: 69 additions & 0 deletions src/background/services/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { connect, TimeTrackerStoreTables } from '../../shared/db/idb';
import { logMessage } from '../tables/logs';

const getIdsToDelete = async (cutOffDate: Date) => {
await logMessage(`Cut Off Date: ${cutOffDate.getTime()}`);
const db = await connect();
const timeline = await db.getAll(
TimeTrackerStoreTables.Timeline,
);
const idsToDelete: number[] = [];

for (const data of timeline) {
const recordDate = new Date(data.date).getTime();
if (recordDate < cutOffDate.getTime()) {
const id: number = data.id as number;
// Should be deleted
idsToDelete.push(id);
}
}

return idsToDelete;
};

const deleteRecordsById = async (idsToDelete: number[]): Promise<string> => {
try {

const db = await connect();
const transaction = db.transaction(
TimeTrackerStoreTables.Timeline,
'readwrite',
);
const store = transaction.objectStore(TimeTrackerStoreTables.Timeline);

for (const id of idsToDelete) {
// Using any as delete function expects string but we store id as number
// this throws lint error
const delId: any = id;
await store.delete(delId);
}

await transaction.done;
await logMessage('Records deleted successfully.');
return 'Records deleted successfully.';
} catch (error) {
console.error('Error deleting IDB records:', error);
return 'Failed to delete records.';
}
};

const manageLocalStoreSpace = async (
defaultCutOff: number,
): Promise<string> => {
await logMessage(`Handling Old Data Cleanup`);
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - defaultCutOff);
const idsToDelete = await getIdsToDelete(cutoffDate);

await logMessage(
`Records to Delete: ${JSON.stringify(idsToDelete, null, 2)}`,
);

if (idsToDelete.length > 0) {
return await deleteRecordsById(idsToDelete);
}

return `No timeline data available for keeping beyond the ${defaultCutOff} days period.`
};

export { manageLocalStoreSpace };
101 changes: 101 additions & 0 deletions src/popup/components/CleanUpTimeLineStorage/CleanUpTimeLineStorage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as React from "react";
import { Panel, PanelBody, PanelHeader } from "../../../blocks/Panel";
import { Button, ButtonType } from "../../../blocks/Button";
import { handleClearTimeLineData } from "../../../shared/db/helper";
import { Input } from "../../../blocks/Input";
import { usePopupContext } from "../../hooks/PopupContext";

export const CleanUpTimeLineStorage: React.FC = () => {
const { settings, updateSettings } = usePopupContext()
const [state, setState] = React.useState<{
cutOffDate: number | string
status: boolean,
statusText: string,
}>({
cutOffDate: '',
status: false,
statusText: '',
});

const handleClearData = React.useCallback(() => {
const days = Number(state.cutOffDate);
if (days < 1) {
setState((prev) => ({
...prev,
status: true,
statusText: 'Cut Off Date cannot be lower than 1'
}));
return;
}

handleClearTimeLineData(days)
.then((res) => {
// Update state
setState((prev) => ({
...prev,
status: true,
statusText: res,
}));

// Update settings
updateSettings({
timeLineCleanUpDays: days
});
})
}, [state, updateSettings]);

const handleCutOffDate = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setState((prev) => ({
...prev,
cutOffDate: Number(e.target.value),
}));
},
[],
);

React.useEffect(() => {
const { timeLineCleanUpDays } = settings
if (timeLineCleanUpDays) {
setState((prev) => ({
...prev,
cutOffDate: timeLineCleanUpDays
}));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const { cutOffDate, status, statusText } = state;
return (
<Panel>
<PanelHeader>Clean Up Old Timeline Data</PanelHeader>
<PanelBody className="flex flex-col gap-2">
<p>Enter the number of days you wish to retain your data. Data older than this period will be removed.</p>
<div className="flex justify-between items-end gap-2">
<label className="flex flex-col gap-1 w-full">
<Input
placeholder="e.g. 10"
value={cutOffDate}
onChange={handleCutOffDate}
type="number"
min={1}
/>
</label>
<Button
className="h-fit py-2 px-4 border-2 border-solid border-transparent"
buttonType={ButtonType.Primary}
onClick={handleClearData}
>
Clear
</Button>
</div>
<p className="text-gray-400">After clicking the Clear button, only the timeline data from your local storage will be cleared. The synced data will remain in the server.</p>
<div className="flex justify-between items-end gap-2">
{status
&& (<p className="text-yellow-600">{statusText}</p>)
}
</div>
</PanelBody>
</Panel>
)
}
2 changes: 2 additions & 0 deletions src/popup/pages/PreferencesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Input } from './../../blocks/Input';
import {IgnoredDomainSetting} from '../components/IgnoredDomainsSetting/IgnoredDomainSetting';
import { WhitelistDomainSetting } from '../components/WhitelistDomainsSetting/WhitelistDomainSetting';
import {UserTokenSetting} from "../components/UserTokenSetting/UserTokenSetting";
import { CleanUpTimeLineStorage } from '../components/CleanUpTimeLineStorage/CleanUpTimeLineStorage';

export const PreferencesPage: FC = () => {
const [isWhitelistShown, hideWhitelist] = React.useState<boolean>(true);
Expand Down Expand Up @@ -45,6 +46,7 @@ export const PreferencesPage: FC = () => {
{(isWhitelistShown ? <WhitelistDomainSetting />: <IgnoredDomainSetting />)}
</PanelBody>
</Panel>
<CleanUpTimeLineStorage />
</div>
);
};
5 changes: 5 additions & 0 deletions src/shared/db/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { manageLocalStoreSpace } from "../../background/services/data";

export const handleClearTimeLineData = async (days: number) => {
return await manageLocalStoreSpace(days)
};
2 changes: 2 additions & 0 deletions src/shared/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface TimelineRecord {
secure: boolean;
activityPeriodStart: number;
activityPeriodEnd: number;
id?: number;
}

export type ActiveTabState = {
Expand Down Expand Up @@ -61,6 +62,7 @@ export interface Preferences {
limits: Record<string, number>;
displayTimeOnBadge: boolean;
lastUpdateStats?: Statistics;
timeLineCleanUpDays?: number;
}

export interface Statistics {
Expand Down