2
0
mirror of https://github.com/frappe/books.git synced 2025-01-08 17:24:05 +00:00

feat: add singleton TelemetryManager

- change config to ts
- add docnames types
This commit is contained in:
18alantom 2022-03-09 15:43:17 +05:30
parent d0e10d5c65
commit 159d943593
6 changed files with 268 additions and 4 deletions

53
models/types.ts Normal file
View File

@ -0,0 +1,53 @@
export enum DoctypeName {
SetupWizard = 'SetupWizard',
Currency = 'Currency',
Color = 'Color',
Account = 'Account',
AccountingSettings = 'AccountingSettings',
CompanySettings = 'CompanySettings',
AccountingLedgerEntry = 'AccountingLedgerEntry',
Party = 'Party',
Customer = 'Customer',
Supplier = 'Supplier',
Payment = 'Payment',
PaymentFor = 'PaymentFor',
PaymentSettings = 'PaymentSettings',
Item = 'Item',
SalesInvoice = 'SalesInvoice',
SalesInvoiceItem = 'SalesInvoiceItem',
SalesInvoiceSettings = 'SalesInvoiceSettings',
PurchaseInvoice = 'PurchaseInvoice',
PurchaseInvoiceItem = 'PurchaseInvoiceItem',
PurchaseInvoiceSettings = 'PurchaseInvoiceSettings',
Tax = 'Tax',
TaxDetail = 'TaxDetail',
TaxSummary = 'TaxSummary',
GSTR3B = 'GSTR3B',
Address = 'Address',
Contact = 'Contact',
JournalEntry = 'JournalEntry',
JournalEntryAccount = 'JournalEntryAccount',
JournalEntrySettings = 'JournalEntrySettings',
Quotation = 'Quotation',
QuotationItem = 'QuotationItem',
QuotationSettings = 'QuotationSettings',
SalesOrder = 'SalesOrder',
SalesOrderItem = 'SalesOrderItem',
SalesOrderSettings = 'SalesOrderSettings',
Fulfillment = 'Fulfillment',
FulfillmentItem = 'FulfillmentItem',
FulfillmentSettings = 'FulfillmentSettings',
PurchaseOrder = 'PurchaseOrder',
PurchaseOrderItem = 'PurchaseOrderItem',
PurchaseOrderSettings = 'PurchaseOrderSettings',
PurchaseReceipt = 'PurchaseReceipt',
PurchaseReceiptItem = 'PurchaseReceiptItem',
PurchaseReceiptSettings = 'PurchaseReceiptSettings',
Event = 'Event',
EventSchedule = 'EventSchedule',
EventSettings = 'EventSettings',
Email = 'Email',
EmailAccount = 'EmailAccount',
PrintSettings = 'PrintSettings',
GetStarted = 'GetStarted',
}

View File

@ -1,4 +0,0 @@
import Store from 'electron-store';
let config = new Store();
export default config;

17
src/config.ts Normal file
View File

@ -0,0 +1,17 @@
import Store from 'electron-store';
const config = new Store();
export default config;
export enum ConfigKeys {
Files = 'files',
LastSelectedFilePath = 'lastSelectedFilePath',
Language = 'language',
DeviceId = 'deviceId',
}
export interface ConfigFile {
id: string;
companyName: string;
filePath: string;
}

120
src/telemetry/helpers.ts Normal file
View File

@ -0,0 +1,120 @@
import config, { ConfigFile, ConfigKeys } from '@/config';
import { DoctypeName } from '../../models/types';
import { Count, Locale, UniqueId } from './types';
export function getId(): string {
let id: string = '';
for (let i = 0; i < 4; i++) {
id += Math.random().toString(36).slice(2, 9);
}
return id;
}
export function getLocale(): Locale {
// @ts-ignore
const country: string = frappe.AccountingSettings.country;
const language: string = config.get('language') as string;
return { country, language };
}
export async function getCounts(): Promise<Count> {
const interestingDocs = [
DoctypeName.Payment,
DoctypeName.PaymentFor,
DoctypeName.SalesInvoice,
DoctypeName.SalesInvoiceItem,
DoctypeName.PurchaseInvoice,
DoctypeName.PurchaseInvoiceItem,
DoctypeName.JournalEntry,
DoctypeName.JournalEntryAccount,
DoctypeName.Account,
DoctypeName.Tax,
];
const countMap: Count = {};
type CountResponse = { 'count(*)': number }[];
for (const name of interestingDocs) {
// @ts-ignore
const queryResponse: CountResponse = await frappe.db.knex(name).count();
const count: number = queryResponse[0]['count(*)'];
countMap[name] = count;
}
// @ts-ignore
const supplierCount: CountResponse = await frappe.db
.knex('Party')
.count()
.where({ supplier: 1 });
// @ts-ignore
const customerCount: CountResponse = await frappe.db
.knex('Party')
.count()
.where({ customer: 1 });
countMap[DoctypeName.Customer] = customerCount[0]['count(*)'];
countMap[DoctypeName.Supplier] = supplierCount[0]['count(*)'];
return countMap;
}
export function getDeviceId(): UniqueId {
let deviceId = config.get(ConfigKeys.DeviceId) as string | undefined;
if (deviceId === undefined) {
deviceId = getId();
config.set(ConfigKeys.DeviceId, deviceId);
}
return deviceId;
}
export function getInstanceId(): UniqueId {
const files = config.get(ConfigKeys.Files) as ConfigFile[];
// @ts-ignore
const companyName = frappe.AccountingSettings.companyName;
const file = files.find((f) => f.companyName === companyName);
if (file === undefined) {
return addNewFile(companyName, files);
}
if (file.id === undefined) {
return setInstanceId(companyName, files);
}
return file.id;
}
function addNewFile(companyName: string, files: ConfigFile[]): UniqueId {
const newFile: ConfigFile = {
companyName,
filePath: config.get(ConfigKeys.LastSelectedFilePath, '') as string,
id: getId(),
};
files.push(newFile);
config.set(ConfigKeys.Files, files);
return newFile.id;
}
function setInstanceId(companyName: string, files: ConfigFile[]): UniqueId {
let id = '';
for (const file of files) {
if (file.id) {
continue;
}
file.id = getId();
if (file.companyName === companyName) {
id = file.id;
}
}
config.set(ConfigKeys.Files, files);
return id;
}

View File

@ -0,0 +1,38 @@
import { getCounts, getDeviceId, getInstanceId, getLocale } from './helpers';
import { Noun, Telemetry, Verb } from './types';
class TelemetryManager {
#started = false;
#telemetryObject: Partial<Telemetry> = {};
start() {
if (this.#started) {
return;
}
this.#telemetryObject.locale = getLocale();
this.#telemetryObject.deviceId = getDeviceId();
this.#telemetryObject.instanceId = getInstanceId();
this.#telemetryObject.openTime = new Date().valueOf();
this.#telemetryObject.timeline = [];
this.#started = true;
}
log(verb: Verb, noun: Noun, more?: Record<string, unknown>) {
if (!this.#started) {
this.start();
}
const time = new Date().valueOf();
if (this.#telemetryObject.timeline === undefined) {
this.#telemetryObject.timeline = [];
}
this.#telemetryObject.timeline.push({ time, verb, noun, more });
}
async stop() {
this.#telemetryObject.counts = await getCounts();
this.#telemetryObject.closeTime = new Date().valueOf();
}
}
export const telemetryManager = new TelemetryManager();

40
src/telemetry/types.ts Normal file
View File

@ -0,0 +1,40 @@
import { DoctypeName } from 'models/types';
export type UniqueId = string;
export type Timestamp = number;
export interface InteractionEvent {
time: Timestamp;
verb: Verb;
noun: Noun;
more?: Record<string, unknown>;
}
export interface Locale {
country: string;
language: string;
}
export type Count = Partial<{
[key in DoctypeName]: number;
}>;
export interface Telemetry {
deviceId: UniqueId;
instanceId: UniqueId;
openTime: Timestamp;
closeTime: Timestamp;
timeline?: InteractionEvent[];
counts?: Count;
locale: Locale;
}
export enum Verb {
Saved = 'saved',
Submitted = 'sumbitted',
Canceled = 'canceled',
Deleted = 'deleted',
Navigated = 'navigated',
}
export enum Noun {}