Skip to content

Commit

Permalink
add executeIntegration()
Browse files Browse the repository at this point in the history
  • Loading branch information
dangowans committed Jan 10, 2025
1 parent 198fcba commit daf5ccf
Show file tree
Hide file tree
Showing 13 changed files with 3,263 additions and 3,397 deletions.
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ representative at an online or offline event.

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
**[email protected]**.
**<[email protected]>**.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
Expand Down
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
[![Maintainability](https://api.codeclimate.com/v1/badges/0d91e0c07b4647f628e6/maintainability)](https://codeclimate.com/github/cityssm/node-faster-unofficial-api/maintainability)

**An _unofficial_ API for the
[FASTER Web fleet management system](https://fasterasset.com/products/fleet-management-software/),
relying on exported reports and complex parsers.**
[FASTER Web fleet management system](https://fasterasset.com/products/fleet-management-software/)
relying on Puppeteer scripts, exported reports, and complex parsers.**

This API ties together the following two projects:
This API uses the following two projects:

- [FASTER Web Report Exporter - @cityssm/faster-web-exporter](https://www.npmjs.com/package/@cityssm/faster-report-exporter)<br />
On demand exports of selected reports from the FASTER Web Fleet Management System.
Expand Down Expand Up @@ -36,14 +36,13 @@ const fasterApi = new FasterUnofficialAPI(
const assets = await fasterApi.getAssets()

const inventory = await fasterApi.getInventory()

const success =
await fasterApi.executeIntegration('Inventory Import Utility')
```

## More Code for FASTER Web
## Related Projects

[FASTER Web Helper](https://github.com/cityssm/faster-web-helper)<br />
A service to support integrations with the FASTER Web fleet management system.
_Building an intergration with FASTER Web?_

[Userscripts for FASTER Web](https://cityssm.github.io/userscripts/#userscripts-for-faster-web)<br />
Fixes some of the common irks when using FASTER Web.
Includes userscripts to enforce field validation, correct varying header heights,
and offer autocomplete.
[Have a look at the City's open source projects related to FASTER Web](https://github.com/cityssm/faster-web-projects).
4 changes: 3 additions & 1 deletion eslint.config.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { default } from 'eslint-config-cityssm';
import { type Config } from 'eslint-config-cityssm';
declare const config: Config;
export default config;
19 changes: 18 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
export { default } from 'eslint-config-cityssm';
import eslintConfigCityssm, { cspellWords, tseslint } from 'eslint-config-cityssm';
const config = tseslint.config(...eslintConfigCityssm, {
rules: {
'@cspell/spellchecker': [
'warn',
{
cspell: {
words: [...cspellWords, 'fasterwebcloud']
}
}
],
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off'
}
});
export default config;
25 changes: 24 additions & 1 deletion eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
export { default } from 'eslint-config-cityssm'
import eslintConfigCityssm, {
type Config,
cspellWords,
tseslint
} from 'eslint-config-cityssm'

const config = tseslint.config(...eslintConfigCityssm, {
rules: {
'@cspell/spellchecker': [
'warn',
{
cspell: {
words: [...cspellWords, 'fasterwebcloud']
}
}
],
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off'
}
}) as Config

export default config
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { csvReports, xlsxReports } from '@cityssm/faster-report-parser';
export type FasterUnofficialAPIOptions = Omit<FasterReportExporterOptions, 'downloadFolderPath'>;
export declare class FasterUnofficialAPI {
#private;
constructor(fasterTenant: string, fasterUserName: string, fasterPassword: string, options?: Partial<FasterUnofficialAPIOptions>);
constructor(fasterTenantOrBaseUrl: string, fasterUserName: string, fasterPassword: string, options?: Partial<FasterUnofficialAPIOptions>);
getAssets(): Promise<xlsxReports.W114AssetReportData[]>;
getInventory(): Promise<xlsxReports.W200StoreroomReportData[]>;
executeIntegration(integrationName: string): Promise<boolean>;
}
export declare const integrationNames: {
inventoryImportUtility: string;
};
export declare const parser: {
csvReports: typeof csvReports;
xlsxReports: typeof xlsxReports;
Expand Down
45 changes: 43 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { FasterReportExporter } from '@cityssm/faster-report-exporter';
import { csvReports, xlsxReports } from '@cityssm/faster-report-parser';
import { minutesToMillis } from '@cityssm/to-millis';
import Debug from 'debug';
import { deleteFile } from './utilities.js';
const debug = Debug('faster-unofficial-api:index');
const integrationsTimeoutMillis = minutesToMillis(1);
export class FasterUnofficialAPI {
#fasterReportExporter;
constructor(fasterTenant, fasterUserName, fasterPassword, options = {}) {
this.#fasterReportExporter = new FasterReportExporter(fasterTenant, fasterUserName, fasterPassword, options);
constructor(fasterTenantOrBaseUrl, fasterUserName, fasterPassword, options = {}) {
this.#fasterReportExporter = new FasterReportExporter(fasterTenantOrBaseUrl, fasterUserName, fasterPassword, options);
}
async getAssets() {
debug('Exporting asset list...');
Expand All @@ -28,7 +30,46 @@ export class FasterUnofficialAPI {
await deleteFile(inventoryReportPath);
return report.data;
}
async executeIntegration(integrationName) {
const { browser, page } = await this.#fasterReportExporter._getLoggedInFasterPage();
try {
await page.goto(this.#fasterReportExporter.fasterUrlBuilder.integrationsUrl, {
timeout: integrationsTimeoutMillis
});
await page.waitForNetworkIdle({
timeout: integrationsTimeoutMillis
});
const integrationTableRowElements = await page.$$('#ctl00_ContentPlaceHolder_Content_IntegrationRadDock_C_IntegrationRadGrid_ctl00 tbody tr');
for (const integrationTableRowElement of integrationTableRowElements) {
const integrationNameElement = await integrationTableRowElement.$('td:nth-child(1) a');
if (integrationNameElement === null) {
continue;
}
const integrationNameText = await integrationNameElement.evaluate((cell) => cell.textContent);
if (integrationNameText === integrationName) {
const integrationActionLinkElements = await integrationTableRowElement.$$('td:nth-child(3) a');
for (const integrationActionLinkElement of integrationActionLinkElements) {
const integrationActionLinkText = await integrationActionLinkElement.evaluate((cell) => cell.textContent);
if (integrationActionLinkText === 'Execute') {
await integrationActionLinkElement.click();
return true;
}
}
}
}
}
finally {
try {
await browser.close();
}
catch { }
}
return false;
}
}
export const integrationNames = {
inventoryImportUtility: 'Inventory Import Utility'
};
export const parser = {
csvReports,
xlsxReports
Expand Down
94 changes: 92 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type FasterReportExporterOptions
} from '@cityssm/faster-report-exporter'
import { csvReports, xlsxReports } from '@cityssm/faster-report-parser'
import { minutesToMillis } from '@cityssm/to-millis'
import Debug from 'debug'

import { deleteFile } from './utilities.js'
Expand All @@ -14,23 +15,37 @@ export type FasterUnofficialAPIOptions = Omit<
'downloadFolderPath'
>

const integrationsTimeoutMillis = minutesToMillis(1)

export class FasterUnofficialAPI {
readonly #fasterReportExporter: FasterReportExporter

/**
* Initialize the Faster Unofficial API
* @param fasterTenantOrBaseUrl - The subdomain of the FASTER Web URL before ".fasterwebcloud.com"
* or the full domain and path including "/FASTER"
* @param fasterUserName - The username to log in with
* @param fasterPassword - The password to log in with
* @param options - Additional options
*/
constructor(
fasterTenant: string,
fasterTenantOrBaseUrl: string,
fasterUserName: string,
fasterPassword: string,
options: Partial<FasterUnofficialAPIOptions> = {}
) {
this.#fasterReportExporter = new FasterReportExporter(
fasterTenant,
fasterTenantOrBaseUrl,
fasterUserName,
fasterPassword,
options
)
}

/**
* Retrieves a list of assets using the W114 report.
* @returns A list of assets
*/
async getAssets(): Promise<xlsxReports.W114AssetReportData[]> {
debug('Exporting asset list...')

Expand All @@ -50,6 +65,10 @@ export class FasterUnofficialAPI {
return report.data
}

/**
* Retrieves a list of inventory items using the W200 report.
* @returns A list of inventory items, grouped by storeroom
*/
async getInventory(): Promise<xlsxReports.W200StoreroomReportData[]> {
debug('Exporting inventory report...')

Expand All @@ -68,6 +87,77 @@ export class FasterUnofficialAPI {

return report.data
}

/**
* Executes an integration by name.
* @param integrationName - The name of the integration to execute
* @returns `true` if the integration was executed, false if not
*/
async executeIntegration(integrationName: string): Promise<boolean> {
const { browser, page } =
await this.#fasterReportExporter._getLoggedInFasterPage()

try {
await page.goto(
this.#fasterReportExporter.fasterUrlBuilder.integrationsUrl,
{
timeout: integrationsTimeoutMillis
}
)

await page.waitForNetworkIdle({
timeout: integrationsTimeoutMillis
})

// Find the integration row

const integrationTableRowElements = await page.$$(
// eslint-disable-next-line no-secrets/no-secrets
'#ctl00_ContentPlaceHolder_Content_IntegrationRadDock_C_IntegrationRadGrid_ctl00 tbody tr'
)

for (const integrationTableRowElement of integrationTableRowElements) {
const integrationNameElement =
await integrationTableRowElement.$('td:nth-child(1) a')

if (integrationNameElement === null) {
continue
}

const integrationNameText = await integrationNameElement.evaluate(
(cell) => cell.textContent
)

if (integrationNameText === integrationName) {
const integrationActionLinkElements =
await integrationTableRowElement.$$('td:nth-child(3) a')

for (const integrationActionLinkElement of integrationActionLinkElements) {
const integrationActionLinkText =
await integrationActionLinkElement.evaluate(
(cell) => cell.textContent
)

if (integrationActionLinkText === 'Execute') {
await integrationActionLinkElement.click()

return true
}
}
}
}
} finally {
try {
await browser.close()
} catch {}
}

return false
}
}

export const integrationNames = {
inventoryImportUtility: 'Inventory Import Utility'
}

export const parser = {
Expand Down
Loading

0 comments on commit daf5ccf

Please sign in to comment.