mirror of
https://github.com/frappe/books.git
synced 2025-01-24 07:38:25 +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…
x
Reference in New Issue
Block a user