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:
parent
d0e10d5c65
commit
159d943593
53
models/types.ts
Normal file
53
models/types.ts
Normal 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',
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
import Store from 'electron-store';
|
||||
|
||||
let config = new Store();
|
||||
export default config;
|
17
src/config.ts
Normal file
17
src/config.ts
Normal 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
120
src/telemetry/helpers.ts
Normal 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;
|
||||
}
|
38
src/telemetry/telemetry.ts
Normal file
38
src/telemetry/telemetry.ts
Normal 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
40
src/telemetry/types.ts
Normal 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 {}
|
Loading…
Reference in New Issue
Block a user