Skip to content

Commit

Permalink
Add types to Document route; Document-related cleanup (#324)
Browse files Browse the repository at this point in the history
* Define SessionService exports

* WIP convert document route to TS

* Update modifiedTime

* Remove thumbnail, update Tile modifiedAgo

* Cleanup

* Fix modifiedTime type

* Update `modifiedAgo` and `timeAgo`

* Add tests

* Rework "modifiedTime" calculations

* Fix sidebar timeAgo; add documentation

* Add "Modified" to tile

* Remove console.logs

* Update document.ts

* Fix incorrect types and test

* Add created types

* Clarify types
  • Loading branch information
jeffdaley authored Sep 20, 2023
1 parent 3833392 commit 44e8f17
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 64 deletions.
1 change: 0 additions & 1 deletion web/app/components/dashboard/latest-updates.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
@owner={{get doc.owners 0}}
@productArea={{doc.product}}
@status={{lowercase doc.status}}
@thumbnail={{doc.googleMetadata.thumbnailLink}}
@title={{doc.title}}
/>
{{/each}}
Expand Down
3 changes: 0 additions & 3 deletions web/app/components/dashboard/latest-updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import ConfigService from "hermes/services/config";
import { HermesDocument } from "hermes/types/document";
import { SearchResponse } from "instantsearch.js";

// @ts-ignore - not yet typed
import timeAgo from "hermes/utils/time-ago";

interface DashboardLatestUpdatesComponentSignature {
Args: {};
}
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/doc/row.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@route="authenticated.document"
@model="{{@docID}}"
@query={{hash draft=@isDraft}}
class="flex space-x-4 items-start"
class="flex items-start space-x-4"
>
<Doc::Thumbnail @status={{@status}} @product={{@productArea}} />
<div>
Expand Down
1 change: 0 additions & 1 deletion web/app/components/doc/tile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ interface DocTileComponentSignature {
productArea?: string;
snippet?: string;
status?: string;
thumbnail?: string;
title?: string;
};
}
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/document/sidebar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@

<div class="flex flex-col items-start space-y-2">
<Document::Sidebar::SectionHeader @title="Created" />
<p>{{or @document.createdDate "Unknown"}}</p>
<p>{{or (parse-date @document.created "long") "Unknown"}}</p>
</div>

<div class="flex flex-col items-start space-y-2">
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/row-results.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</H.Tr>
</:head>
<:body>
{{#each @docs as |doc index|}}
{{#each @docs as |doc|}}
<Doc::Row
@avatar="{{get doc.ownerPhotos 0}}"
@createdDate="{{parse-date doc.created}}"
Expand Down
3 changes: 3 additions & 0 deletions web/app/controllers/authenticated/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import Controller from "@ember/controller";
import { inject as service } from "@ember/service";
import AuthenticatedUserService from "hermes/services/authenticated-user";
import RecentlyViewedDocsService from "hermes/services/recently-viewed-docs";
import { HermesDocument } from "hermes/types/document";

export default class AuthenticatedDashboardController extends Controller {
@service declare authenticatedUser: AuthenticatedUserService;
@service("recently-viewed-docs")
declare recentDocs: RecentlyViewedDocsService;

declare model: HermesDocument[];
}
7 changes: 2 additions & 5 deletions web/app/helpers/parse-date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@ export interface ParseDateHelperSignature {
Args: {
Positional: [
time: string | number | Date | undefined,
monthFormat: "short" | "long"
monthFormat?: "short" | "long"
];
Return: Date | undefined;
};
}

const parseDateHelper = helper<ParseDateHelperSignature>(
([time, monthFormat = "short"]: [
string | number | Date | undefined,
"short" | "long"
]) => {
([time, monthFormat = "short"]) => {
return parseDate(time, monthFormat);
}
);
Expand Down
1 change: 0 additions & 1 deletion web/app/routes/authenticated/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import FetchService from "hermes/services/fetch";
import RecentlyViewedDocsService from "hermes/services/recently-viewed-docs";
import SessionService from "hermes/services/session";
import AuthenticatedUserService from "hermes/services/authenticated-user";
import timeAgo from "hermes/utils/time-ago";
import { HermesDocument } from "hermes/types/document";

export default class DashboardRoute extends Route {
Expand Down
109 changes: 66 additions & 43 deletions web/app/routes/authenticated/document.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
// @ts-nocheck - Not yet typed
import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";
import timeAgo from "hermes/utils/time-ago";
import RSVP from "rsvp";
import parseDate from "hermes/utils/parse-date";
import htmlElement from "hermes/utils/html-element";
import { scheduleOnce } from "@ember/runloop";

const serializePeople = (people) =>
people.map((p) => ({
email: p.emailAddresses[0].value,
import { schedule } from "@ember/runloop";
import { GoogleUser } from "hermes/components/inputs/people-select";
import ConfigService from "hermes/services/config";
import FetchService from "hermes/services/fetch";
import RecentlyViewedDocsService from "hermes/services/recently-viewed-docs";
import AlgoliaService from "hermes/services/algolia";
import SessionService from "hermes/services/session";
import FlashMessageService from "ember-cli-flash/services/flash-messages";
import RouterService from "@ember/routing/router-service";
import { HermesDocument, HermesUser } from "hermes/types/document";
import Transition from "@ember/routing/transition";
import { HermesDocumentType } from "hermes/types/document-type";
import AuthenticatedDocumentController from "hermes/controllers/authenticated/document";

const serializePeople = (people: GoogleUser[]): HermesUser[] => {
return people.map((p) => ({
email: p.emailAddresses[0]?.value as string,
imgURL: p.photos?.[0]?.url,
}));
};

interface DocumentRouteParams {
document_id: string;
draft: boolean;
}

interface DocumentRouteModel {
doc: HermesDocument;
docType: HermesDocumentType;
}

export default class DocumentRoute extends Route {
@service algolia;
@service("config") configSvc;
@service("fetch") fetchSvc;
@service("recently-viewed-docs") recentDocs;
@service session;
@service flashMessages;
@service router;
@service("config") declare configSvcL: ConfigService;
@service("fetch") declare fetchSvc: FetchService;
@service("recently-viewed-docs")
declare recentDocs: RecentlyViewedDocsService;
@service declare algolia: AlgoliaService;
@service declare session: SessionService;
@service declare flashMessages: FlashMessageService;
@service declare router: RouterService;

declare controller: AuthenticatedDocumentController;

// Ideally we'd refresh the model when the draft query param changes, but
// because of a suspected bug in Ember, we can't do that.
Expand All @@ -31,7 +54,7 @@ export default class DocumentRoute extends Route {
// },
// };

showErrorMessage(err) {
showErrorMessage(err: Error) {
this.flashMessages.add({
title: "Error fetching document",
message: err.message,
Expand All @@ -41,7 +64,7 @@ export default class DocumentRoute extends Route {
});
}

async model(params, transition) {
async model(params: DocumentRouteParams, transition: Transition) {
let doc = {};
let draftFetched = false;

Expand All @@ -57,9 +80,8 @@ export default class DocumentRoute extends Route {
"Add-To-Recently-Viewed": "true",
},
})
.then((r) => r.json());

doc.isDraft = params.draft;
.then((r) => r?.json());
(doc as HermesDocument).isDraft = params.draft;
draftFetched = true;
} catch (err) {
/**
Expand All @@ -85,25 +107,24 @@ export default class DocumentRoute extends Route {
"Add-To-Recently-Viewed": "true",
},
})
.then((r) => r.json());
.then((r) => r?.json());

doc.isDraft = false;
(doc as HermesDocument).isDraft = false;
} catch (err) {
this.showErrorMessage(err);
const typedError = err as Error;
this.showErrorMessage(typedError);

// Transition to dashboard
this.router.transitionTo("authenticated.dashboard");
throw new Error(errorMessage);
throw new Error(typedError.message);
}
}

// With the document fetched and added to the db's RecentlyViewedDocs index,
// make a background call to update the front-end index.
void this.recentDocs.fetchAll.perform();

if (!!doc.createdTime) {
doc.createdDate = parseDate(doc.createdTime * 1000, "long");
}
let typedDoc = doc as HermesDocument;

// Record analytics.
try {
Expand All @@ -112,7 +133,7 @@ export default class DocumentRoute extends Route {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
document_id: params.document_id,
product_name: doc.product,
product_name: typedDoc.product,
}),
});
} catch (err) {
Expand All @@ -122,37 +143,39 @@ export default class DocumentRoute extends Route {
// Load the document as well as the logged in user info

// Preload avatars for all approvers in the Algolia index.
if (doc.contributors?.length) {
if (typedDoc.contributors?.length) {
const contributors = await this.fetchSvc
.fetch(`/api/v1/people?emails=${doc.contributors.join(",")}`)
.then((r) => r.json());
.fetch(`/api/v1/people?emails=${typedDoc.contributors.join(",")}`)
.then((r) => r?.json());

if (contributors) {
doc.contributors = serializePeople(contributors);
typedDoc.contributors = serializePeople(contributors);
} else {
doc.contributors = [];
typedDoc.contributors = [];
}
}
if (doc.approvers?.length) {
if (typedDoc.approvers?.length) {
const approvers = await this.fetchSvc
.fetch(`/api/v1/people?emails=${doc.approvers.join(",")}`)
.then((r) => r.json());
.fetch(`/api/v1/people?emails=${typedDoc.approvers.join(",")}`)
.then((r) => r?.json());

if (approvers) {
doc.approvers = serializePeople(approvers);
typedDoc.approvers = serializePeople(approvers);
} else {
doc.approvers = [];
typedDoc.approvers = [];
}
}

let docTypes = await this.fetchSvc
.fetch("/api/v1/document-types")
.then((r) => r.json());
.then((r) => r?.json());

let docType = docTypes.find((docType) => docType.name === doc.docType);
let docType = docTypes.find(
(docType: HermesDocumentType) => docType.name === typedDoc.docType
);

return RSVP.hash({
doc,
doc: typedDoc,
docType,
});
}
Expand All @@ -164,7 +187,7 @@ export default class DocumentRoute extends Route {
* `modelIsChanging` property to remove and rerender the sidebar,
* resetting its local state to reflect the new model data.
*/
afterModel(model, transition) {
afterModel(_model: DocumentRouteModel, transition: any) {
if (transition.from) {
if (transition.from.name === transition.to.name) {
if (
Expand All @@ -175,7 +198,7 @@ export default class DocumentRoute extends Route {

htmlElement(".sidebar-body").scrollTop = 0;

scheduleOnce("afterRender", () => {
schedule("afterRender", () => {
this.controller.set("modelIsChanging", false);
});
}
Expand Down
26 changes: 20 additions & 6 deletions web/app/types/document.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { GoogleUser } from "hermes/components/inputs/people-select";

/**
* NOTE: This is a partial type definition.
* We are defining it incrementally as we expand TS coverage.
*/
export interface HermesDocument {
readonly objectID: string;

status: string;
product?: string;
modifiedTime?: number; // Not available on drafts fetched as Hits from backend

/**
* A human-readable date string, e.g., "Aug 16, 2028".
* Mutated in the layout by the `parse-date` helper to place
* the date before the month.
*/
created: string;

/**
* A timestamp in seconds. Used for sorting.
*/
createdTime: number;

/**
* A timestamp in seconds. Used Translated by the `time-ago` helper
* into a human-readable string, e.g., "2 days ago."
* Not available on drafts fetched as Hits from backend.
*/
modifiedTime?: number;

docNumber: string;
docType: string;
title: string;
Expand All @@ -25,7 +40,6 @@ export interface HermesDocument {
isDraft?: boolean;
customEditableFields?: CustomEditableFields;

thumbnail?: string;
_snippetResult?: {
content: {
value: string;
Expand Down
2 changes: 1 addition & 1 deletion web/app/utils/time-ago.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default function timeAgo(timeInSeconds: number) {
const now = Date.now();
const before = new Date(timeInSeconds * 1000).getTime();
const elapsed = now - before;

const elapsedSeconds = elapsed / 1000;

if (elapsedSeconds < 2) {
return "1 second ago";
}
Expand Down

0 comments on commit 44e8f17

Please sign in to comment.