Skip to content

Commit

Permalink
Add active view refresher
Browse files Browse the repository at this point in the history
  • Loading branch information
seratch committed Nov 8, 2023
1 parent c8e8ece commit 1f1b798
Show file tree
Hide file tree
Showing 13 changed files with 756 additions and 118 deletions.
16 changes: 16 additions & 0 deletions datastores/active_views.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DefineDatastore, Schema } from "deno-slack-sdk/mod.ts";

const datastore = DefineDatastore(
{
name: "active_views",
primary_key: "view_id",
attributes: {
view_id: { type: Schema.types.string, required: true },
user_id: { type: Schema.types.string, required: true },
last_updated_at: { type: Schema.types.number, required: true }, // epoch time in seconds
last_updated_callback_id: { type: Schema.types.string, required: true },
},
} as const,
);

export default datastore;
2 changes: 2 additions & 0 deletions datastores/user_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const datastore = DefineDatastore(
country_id: { type: Schema.types.string, required: false },
// work, work_and_lifelogs
app_mode: { type: Schema.types.string, required: false },
// time offset
offset: { type: Schema.types.number, requried: true },
},
} as const,
);
Expand Down
78 changes: 50 additions & 28 deletions functions/internals/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { todayYYYYMMDD } from "./datetime.ts";
import {
AU,
AUMapper,
AV,
AVMapper,
C,
CMapper,
L,
Expand All @@ -25,6 +27,7 @@ import {
import { PrivateMetadata } from "./private_metadata.ts";
import { fetchUserDetails } from "./slack_api.ts";
import { AppModeCode } from "./constants.ts";
import { determineIsDebugMode, determineLogLevel } from "./debug_mode.ts";

export interface ComponentParams {
env: Env;
Expand All @@ -51,6 +54,7 @@ export interface Components {
op: DataMapper<OP>;
au: DataMapper<AU>;
l: DataMapper<L>;
av: DataMapper<AV>;
user: string;
email: string;
settings: SavedAttributes<US>;
Expand All @@ -65,14 +69,8 @@ export interface Components {
export async function injectComponents(
{ env, token, client, inputs: { user_id }, body }: ComponentParams,
): Promise<Components> {
const isDebugMode: boolean = env.DEBUG_MODE !== undefined && (
env.DEBUG_MODE === "1" ||
env.DEBUG_MODE === "T" ||
env.DEBUG_MODE === "TRUE" ||
env.DEBUG_MODE === "True" ||
env.DEBUG_MODE === "true"
);
const logLevel = isDebugMode ? "DEBUG" : "INFO";
const isDebugMode: boolean = determineIsDebugMode(env);
const logLevel = determineLogLevel(env);
const slackApi = new SlackAPI(token, { logLevel });
const user = user_id;
const userInfo = await fetchUserDetails({ slackApi, user: user_id });
Expand All @@ -98,31 +96,18 @@ export async function injectComponents(
if (settings.user) {
language = settings.language;
country = settings.country_id;
if (!settings.offset || settings.offset !== timeOffset) {
const attributes = { ...settings };
attributes.offset = timeOffset;
await us.save({ attributes });
}
}

const ph = PHMapper(client, logLevel);
let _holidays: SavedAttributes<PH> | undefined;
const holidays = async () => {
if (_holidays) return _holidays;
const year = _yyyymmdd.substring(0, 4);
_holidays = (await ph.findById(`${country}-${year}`)).item;
return _holidays;
};
const holidays = buildHolidays(ph, country, _yyyymmdd);

let _canAccessAdminFeature: boolean | undefined;
const au = AUMapper(client, logLevel);
const canAccessAdminFeature = async () => {
if (_canAccessAdminFeature) return _canAccessAdminFeature;
const items = (await au.findAll({ limit: 1 })).items;
const noAdminUsers = items === undefined || items.length === 0;
if (noAdminUsers) {
_canAccessAdminFeature = true;
return _canAccessAdminFeature;
}
const thisUserCanAccess = (await au.findById(user)).item.user !== undefined;
_canAccessAdminFeature = thisUserCanAccess;
return _canAccessAdminFeature;
};
const canAccessAdminFeature = buildCanAccessAdminFeature(au, user);

return {
isDebugMode,
Expand All @@ -144,8 +129,45 @@ export async function injectComponents(
op: OPMapper(client, logLevel),
au,
l: LMapper(client, logLevel),
av: AVMapper(client, logLevel),
holidays,
yyyymmdd: _yyyymmdd,
offset: timeOffset,
};
}

export function buildCanAccessAdminFeature(
au: DataMapper<AU>,
user: string,
): () => Promise<boolean> {
let _canAccessAdminFeature: boolean | undefined;
const canAccessAdminFeature = async () => {
if (_canAccessAdminFeature) return _canAccessAdminFeature;
const items = (await au.findAll({ limit: 1 })).items;
const noAdminUsers = items === undefined || items.length === 0;
if (noAdminUsers) {
_canAccessAdminFeature = true;
return _canAccessAdminFeature;
}
const thisUserCanAccess = (await au.findById(user)).item.user !== undefined;
_canAccessAdminFeature = thisUserCanAccess;
return _canAccessAdminFeature;
};
return canAccessAdminFeature;
}

export function buildHolidays(
ph: DataMapper<PH>,
country: string | undefined,
_yyyymmdd: string,
): () => Promise<SavedAttributes<PH> | undefined> {
let _holidays: SavedAttributes<PH> | undefined;
const holidays = async () => {
if (country === undefined) return undefined;
if (_holidays) return _holidays;
const year = _yyyymmdd.substring(0, 4);
_holidays = (await ph.findById(`${country}-${year}`)).item;
return _holidays;
};
return holidays;
}
58 changes: 57 additions & 1 deletion functions/internals/datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import AdminUsers from "../../datastores/admin_users.ts";
import Projects from "../../datastores/projects.ts";
import OrganizationPolicies from "../../datastores/organization_policies.ts";
import Lifelogs from "../../datastores/lifelogs.ts";
import ActiveViews from "../../datastores/active_views.ts";

import { timeToNumber, todayYYYYMMDD } from "./datetime.ts";
import { CountryCode, Label } from "./constants.ts";
Expand All @@ -29,12 +30,13 @@ export type AU = typeof AdminUsers.definition;
export type P = typeof Projects.definition;
export type OP = typeof OrganizationPolicies.definition;
export type L = typeof Lifelogs.definition;
export type AV = typeof ActiveViews.definition;

// -----------------------------------------
// DataMapper initializer
// -----------------------------------------

function createDataMapper<DEF extends TE | US | C | PH | AU | P | OP | L>(
function createDataMapper<DEF extends TE | US | C | PH | AU | P | OP | L | AV>(
def: DEF,
client: Client,
logLevel: LogLevel,
Expand Down Expand Up @@ -70,6 +72,9 @@ export function OPMapper(client: Client, logLevel: LogLevel): DataMapper<OP> {
export function LMapper(client: Client, logLevel: LogLevel): DataMapper<L> {
return createDataMapper(Lifelogs.definition, client, logLevel);
}
export function AVMapper(client: Client, logLevel: LogLevel): DataMapper<AV> {
return createDataMapper(ActiveViews.definition, client, logLevel);
}

// -----------------------------------------
// TimeEntries
Expand Down Expand Up @@ -494,3 +499,54 @@ export async function fetchProject(
const response = await p.findById(code);
return response.item;
}

// -----------------------------------------
// ActiveViews
// -----------------------------------------

interface saveLastActiveViewArgs {
av: DataMapper<AV>;
view_id: string;
user_id: string;
callback_id: string;
}
export async function saveLastActiveView(
{ av, view_id, user_id, callback_id }: saveLastActiveViewArgs,
) {
const last_updated_at = Math.floor(new Date().getTime() / 1000);
const attributes: Attributes<AV> = {
view_id,
user_id,
last_updated_callback_id: callback_id,
last_updated_at,
};
await av.save({ attributes });
}

interface deleteActiveViewArgs {
av: DataMapper<AV>;
view_id: string;
}
export async function deleteClosingOneFromActiveViews(
{ av, view_id }: deleteActiveViewArgs,
) {
await av.deleteById({ id: view_id });
}

interface cleanUpOldActiveViewsArgs {
av: DataMapper<AV>;
user_id: string;
}
export async function cleanUpOldActiveViews(
{ av, user_id }: cleanUpOldActiveViewsArgs,
) {
const views = (await av.findAllBy({ where: { user_id } })).items;
const oneDayAgo = Math.floor(new Date().getTime() / 1000) - 60 * 60 * 24;
if (views) {
for (const view of views) {
if (view.last_updated_at < oneDayAgo) {
await av.deleteById({ id: view.view_id });
}
}
}
}
17 changes: 17 additions & 0 deletions functions/internals/debug_mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Env } from "deno-slack-sdk/types.ts";

export function determineIsDebugMode(env: Env): boolean {
const isDebugMode: boolean = env.DEBUG_MODE !== undefined && (
env.DEBUG_MODE === "1" ||
env.DEBUG_MODE === "T" ||
env.DEBUG_MODE === "TRUE" ||
env.DEBUG_MODE === "True" ||
env.DEBUG_MODE === "true"
);
return isDebugMode;
}

export function determineLogLevel(env: Env): "DEBUG" | "INFO" {
const logLevel = determineIsDebugMode(env) ? "DEBUG" : "INFO";
return logLevel;
}
58 changes: 26 additions & 32 deletions functions/internals/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RichTextBlock,
SlackAPIClient,
StaticSelect,
ViewsUpdateResponse,
} from "slack-web-api-client/mod.ts";

import { i18n } from "./i18n.ts";
Expand Down Expand Up @@ -110,6 +111,7 @@ export function newView(language: string): ModalView {
"type": "modal",
"title": TitleMain(language),
"close": QuitApp(language),
"notify_on_close": true,
"blocks": [],
};
}
Expand Down Expand Up @@ -298,31 +300,29 @@ export async function syncMainView({
isDebugMode,
isLifelogEnabled,
manualEntryPermitted,
}: syncMainViewArgs) {
}: syncMainViewArgs): Promise<ViewsUpdateResponse> {
const privateMetadata: MainViewPrivateMetadata = { yyyymmdd };
await slackApi.views.update({
view_id: viewId,
view: {
"type": "modal",
"callback_id": CallbackId.MainView,
"private_metadata": JSON.stringify(privateMetadata),
"title": TitleMain(language),
"close": QuitApp(language),
"blocks": await mainViewBlocks({
isDebugMode,
isLifelogEnabled,
manualEntryPermitted,
entry: entry,
lifelog: lifelog,
offset,
language,
country,
holidays,
canAccessAdminFeature,
yyyymmdd,
}),
},
});
const view: ModalView = {
"type": "modal",
"callback_id": CallbackId.MainView,
"private_metadata": JSON.stringify(privateMetadata),
"title": TitleMain(language),
"close": QuitApp(language),
"blocks": await mainViewBlocks({
isDebugMode,
isLifelogEnabled,
manualEntryPermitted,
entry: entry,
lifelog: lifelog,
offset,
language,
country,
holidays,
canAccessAdminFeature,
yyyymmdd,
}),
};
return await slackApi.views.update({ view_id: viewId, view });
}

interface toMainViewArgs {
Expand Down Expand Up @@ -569,12 +569,6 @@ export async function mainViewBlocks({

const reportItems = [];
if (r && r.work_minutes + r.break_time_hours + r.time_off_minutes > 0) {
if (isDebugMode) {
console.log(
"### The daily report for the main view:\n" +
JSON.stringify(r, null, 2),
);
}
const workDuration = [
hourDuration(r.work_hours, language),
minuteDuration(r.work_minutes, language),
Expand Down Expand Up @@ -1628,8 +1622,8 @@ export async function syncProjectMainView({
projects,
slackApi,
language,
}: syncProjectMainViewArgs) {
await slackApi.views.update({
}: syncProjectMainViewArgs): Promise<ViewsUpdateResponse> {
return await slackApi.views.update({
view_id: viewId,
view: toProjectMainView({ view: newView(language), projects, language }),
});
Expand Down
Loading

0 comments on commit 1f1b798

Please sign in to comment.