mirror of
https://github.com/frappe/books.git
synced 2025-01-08 17:24:05 +00:00
incr: simplify telemetry
- it's not opt-in anymore cause my job deps on it
This commit is contained in:
parent
deefcd08f9
commit
2c596b11c8
@ -48,7 +48,12 @@ export async function setupDummyInstance(
|
|||||||
notifier?.(fyo.t`Creating Items and Parties`, -1);
|
notifier?.(fyo.t`Creating Items and Parties`, -1);
|
||||||
await generateStaticEntries(fyo);
|
await generateStaticEntries(fyo);
|
||||||
await generateDynamicEntries(fyo, years, baseCount, notifier);
|
await generateDynamicEntries(fyo, years, baseCount, notifier);
|
||||||
return options.companyName;
|
|
||||||
|
const instanceId = (await fyo.getValue(
|
||||||
|
ModelNameEnum.SystemSettings,
|
||||||
|
'instanceId'
|
||||||
|
)) as string;
|
||||||
|
return { companyName: options.companyName, instanceId };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +34,6 @@ export enum ConfigKeys {
|
|||||||
LastSelectedFilePath = 'lastSelectedFilePath',
|
LastSelectedFilePath = 'lastSelectedFilePath',
|
||||||
Language = 'language',
|
Language = 'language',
|
||||||
DeviceId = 'deviceId',
|
DeviceId = 'deviceId',
|
||||||
Telemetry = 'telemetry',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigFile {
|
export interface ConfigFile {
|
||||||
|
@ -678,6 +678,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
await this.setAndSync('submitted', true);
|
await this.setAndSync('submitted', true);
|
||||||
await this.trigger('afterSubmit');
|
await this.trigger('afterSubmit');
|
||||||
|
|
||||||
|
this.fyo.telemetry.log(Verb.Submitted, this.schemaName);
|
||||||
this.fyo.doc.observer.trigger(`submit:${this.schemaName}`, this.name);
|
this.fyo.doc.observer.trigger(`submit:${this.schemaName}`, this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,6 +691,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
await this.setAndSync('cancelled', true);
|
await this.setAndSync('cancelled', true);
|
||||||
await this.trigger('afterCancel');
|
await this.trigger('afterCancel');
|
||||||
|
|
||||||
|
this.fyo.telemetry.log(Verb.Cancelled, this.schemaName);
|
||||||
this.fyo.doc.observer.trigger(`cancel:${this.schemaName}`, this.name);
|
this.fyo.doc.observer.trigger(`cancel:${this.schemaName}`, this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { ConfigFile, ConfigKeys } from 'fyo/core/types';
|
import { ConfigFile, ConfigKeys } from 'fyo/core/types';
|
||||||
import { DEFAULT_COUNTRY_CODE } from 'fyo/utils/consts';
|
import { DEFAULT_COUNTRY_CODE } from 'fyo/utils/consts';
|
||||||
import { t } from 'fyo/utils/translation';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Count, TelemetrySetting, UniqueId } from './types';
|
import { getRandomString } from 'utils';
|
||||||
|
import { 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 getCountry(fyo: Fyo): string {
|
export function getCountry(fyo: Fyo): string {
|
||||||
return (
|
return (
|
||||||
@ -24,27 +15,10 @@ export function getLanguage(fyo: Fyo): string {
|
|||||||
return fyo.config.get('language') as string;
|
return fyo.config.get('language') as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCounts(
|
|
||||||
interestingDocs: string[],
|
|
||||||
fyo: Fyo
|
|
||||||
): Promise<Count> {
|
|
||||||
const countMap: Count = {};
|
|
||||||
if (fyo.db === undefined) {
|
|
||||||
return countMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const name of interestingDocs) {
|
|
||||||
const count: number = (await fyo.db.getAll(name)).length;
|
|
||||||
countMap[name] = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return countMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDeviceId(fyo: Fyo): UniqueId {
|
export function getDeviceId(fyo: Fyo): UniqueId {
|
||||||
let deviceId = fyo.config.get(ConfigKeys.DeviceId) as string | undefined;
|
let deviceId = fyo.config.get(ConfigKeys.DeviceId) as string | undefined;
|
||||||
if (deviceId === undefined) {
|
if (deviceId === undefined) {
|
||||||
deviceId = getId();
|
deviceId = getRandomString();
|
||||||
fyo.config.set(ConfigKeys.DeviceId, deviceId);
|
fyo.config.set(ConfigKeys.DeviceId, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,69 +27,69 @@ export function getDeviceId(fyo: Fyo): UniqueId {
|
|||||||
|
|
||||||
export function getInstanceId(fyo: Fyo): UniqueId {
|
export function getInstanceId(fyo: Fyo): UniqueId {
|
||||||
const files = (fyo.config.get(ConfigKeys.Files) ?? []) as ConfigFile[];
|
const files = (fyo.config.get(ConfigKeys.Files) ?? []) as ConfigFile[];
|
||||||
|
const instanceId = fyo.singles.SystemSettings!.instanceId as string;
|
||||||
|
const dbPath = fyo.db.dbPath!;
|
||||||
|
const companyName = fyo.singles.AccountingSettings!.companyName as string;
|
||||||
|
|
||||||
const companyName = fyo.singles.AccountingSettings?.companyName as string;
|
let file = files.find((f) => f.id === instanceId);
|
||||||
if (companyName === undefined) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = files.find((f) => f.companyName === companyName);
|
|
||||||
|
|
||||||
if (file === undefined) {
|
if (file === undefined) {
|
||||||
return addNewFile(companyName, fyo, files);
|
file = addNewConfigFile(companyName, dbPath, instanceId, files, fyo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.id === undefined) {
|
if (!file.id) {
|
||||||
return setInstanceId(companyName, files, fyo);
|
setIdOnConfigFile(instanceId, companyName, dbPath, files, fyo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.id;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addNewFile(
|
export function addNewConfigFile(
|
||||||
companyName: string,
|
companyName: string,
|
||||||
fyo: Fyo,
|
dbPath: string,
|
||||||
files?: ConfigFile[],
|
instanceId: string,
|
||||||
dbPath?: string
|
files: ConfigFile[],
|
||||||
): UniqueId {
|
fyo: Fyo
|
||||||
files ??= fyo.config.get(ConfigKeys.Files, []) as ConfigFile[];
|
): ConfigFile {
|
||||||
dbPath ??= fyo.config.get(ConfigKeys.LastSelectedFilePath, '') as string;
|
|
||||||
|
|
||||||
const newFile: ConfigFile = {
|
const newFile: ConfigFile = {
|
||||||
companyName,
|
companyName,
|
||||||
dbPath,
|
dbPath,
|
||||||
id: getId(),
|
id: instanceId,
|
||||||
openCount: 0,
|
openCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
files.push(newFile);
|
files.push(newFile);
|
||||||
fyo.config.set(ConfigKeys.Files, files);
|
fyo.config.set(ConfigKeys.Files, files);
|
||||||
return newFile.id;
|
return newFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setInstanceId(
|
export async function getVersion(fyo: Fyo) {
|
||||||
|
const version = (await fyo.getValue(
|
||||||
|
ModelNameEnum.SystemSettings,
|
||||||
|
'version'
|
||||||
|
)) as string | undefined;
|
||||||
|
|
||||||
|
if (version) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fyo.store.appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIdOnConfigFile(
|
||||||
|
instanceId: string,
|
||||||
companyName: string,
|
companyName: string,
|
||||||
|
dbPath: string,
|
||||||
files: ConfigFile[],
|
files: ConfigFile[],
|
||||||
fyo: Fyo
|
fyo: Fyo
|
||||||
): UniqueId {
|
) {
|
||||||
let id = '';
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.id) {
|
if (file.companyName !== companyName || file.dbPath !== dbPath) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.id = getId();
|
file.id = instanceId;
|
||||||
if (file.companyName === companyName) {
|
|
||||||
id = file.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fyo.config.set(ConfigKeys.Files, files);
|
fyo.config.set(ConfigKeys.Files, files);
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTelemetryOptions = () => ({
|
|
||||||
[TelemetrySetting.allow]: t`Allow Telemetry`,
|
|
||||||
[TelemetrySetting.dontLogUsage]: t`Don't Log Usage`,
|
|
||||||
[TelemetrySetting.dontLogAnything]: t`Don't Log Anything`,
|
|
||||||
});
|
|
||||||
|
@ -1,53 +1,38 @@
|
|||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import {
|
import {
|
||||||
getCountry,
|
getCountry,
|
||||||
getCounts,
|
|
||||||
getDeviceId,
|
getDeviceId,
|
||||||
getInstanceId,
|
getInstanceId,
|
||||||
getLanguage,
|
getLanguage,
|
||||||
|
getVersion,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
import {
|
import { Noun, Platform, Telemetry, Verb } from './types';
|
||||||
Noun,
|
|
||||||
NounEnum,
|
|
||||||
Platform,
|
|
||||||
Telemetry,
|
|
||||||
TelemetrySetting,
|
|
||||||
Verb,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Telemetry
|
* # Telemetry
|
||||||
|
* Used to check if people are using Books or not. All logging
|
||||||
|
* happens using navigator.sendBeacon
|
||||||
*
|
*
|
||||||
* ## `start`
|
* ## `start`
|
||||||
* Used to initialize state. It should be called before interaction.
|
* Used to initialize state. It should be called before any logging and after an
|
||||||
|
* instance has loaded.
|
||||||
* It is called on three events:
|
* It is called on three events:
|
||||||
* 1. On db initialization which happens everytime a db is loaded or changed.
|
* 1. When Desk is opened, i.e. when the usage starts, this also sends a started
|
||||||
|
* log.
|
||||||
* 2. On visibility change if not started, eg: when user minimizeds Books and
|
* 2. On visibility change if not started, eg: when user minimizeds Books and
|
||||||
* then comes back later.
|
* then comes back later.
|
||||||
* 3. When `log` is called if not initialized.
|
* 3. When `log` is called, but telemetry wasn't initialized.
|
||||||
*
|
*
|
||||||
* ## `log`
|
* ## `log`
|
||||||
* Used to make entries in the `timeline` which happens only if telmetry
|
* Used to log activity.
|
||||||
* is set to 'Allow Telemetry`
|
|
||||||
*
|
|
||||||
* ## `error`
|
|
||||||
* Called in errorHandling.ts and maintains a count of errors that were
|
|
||||||
* thrown during usage.
|
|
||||||
*
|
*
|
||||||
* ## `stop`
|
* ## `stop`
|
||||||
* This is to be called when a session is being stopped. It's called on two events
|
* This is to be called when a session is being stopped. It's called on two events
|
||||||
* 1. When the db is being changed.
|
* 1. When the db is being changed.
|
||||||
* 2. When the visiblity has changed which happens when either the app is being shut or
|
* 2. When the visiblity has changed which happens when either the app is being shut or
|
||||||
* the app is hidden.
|
* the app is hidden.
|
||||||
*
|
|
||||||
* This function can't be async as it's called when visibility changes to 'hidden'
|
|
||||||
* at which point async doesn't seem to work and hence count is captured on `start()`
|
|
||||||
*
|
|
||||||
* ## `finalLogAndStop`
|
|
||||||
* Called when telemetry is set to "Don't Log Anything" so as to indicate cessation of
|
|
||||||
* telemetry and not app usage.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class TelemetryManager {
|
export class TelemetryManager {
|
||||||
@ -55,7 +40,6 @@ export class TelemetryManager {
|
|||||||
#token: string = '';
|
#token: string = '';
|
||||||
#started = false;
|
#started = false;
|
||||||
#telemetryObject: Partial<Telemetry> = {};
|
#telemetryObject: Partial<Telemetry> = {};
|
||||||
#interestingDocs: string[] = [];
|
|
||||||
fyo: Fyo;
|
fyo: Fyo;
|
||||||
|
|
||||||
constructor(fyo: Fyo) {
|
constructor(fyo: Fyo) {
|
||||||
@ -66,10 +50,6 @@ export class TelemetryManager {
|
|||||||
this.#telemetryObject.platform ||= value;
|
this.#telemetryObject.platform ||= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
set interestingDocs(schemaNames: string[]) {
|
|
||||||
this.#interestingDocs = schemaNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasCreds() {
|
get hasCreds() {
|
||||||
return !!this.#url && !!this.#token;
|
return !!this.#url && !!this.#token;
|
||||||
}
|
}
|
||||||
@ -82,89 +62,57 @@ export class TelemetryManager {
|
|||||||
return cloneDeep(this.#telemetryObject);
|
return cloneDeep(this.#telemetryObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start(openCount?: number) {
|
||||||
this.#telemetryObject.country ||= getCountry(this.fyo);
|
this.#telemetryObject.country ||= getCountry(this.fyo);
|
||||||
this.#telemetryObject.language ??= getLanguage(this.fyo);
|
this.#telemetryObject.language ??= getLanguage(this.fyo);
|
||||||
this.#telemetryObject.deviceId ||= getDeviceId(this.fyo);
|
this.#telemetryObject.device ||= getDeviceId(this.fyo);
|
||||||
this.#telemetryObject.instanceId ||= getInstanceId(this.fyo);
|
this.#telemetryObject.instance ||= getInstanceId(this.fyo);
|
||||||
this.#telemetryObject.openTime ||= new Date().valueOf();
|
this.#telemetryObject.version ||= await getVersion(this.fyo);
|
||||||
this.#telemetryObject.timeline ??= [];
|
|
||||||
this.#telemetryObject.errors ??= {};
|
|
||||||
this.#telemetryObject.counts ??= {};
|
|
||||||
this.#started = true;
|
this.#started = true;
|
||||||
|
await this.#setCreds();
|
||||||
|
|
||||||
await this.#postStart();
|
if (typeof openCount === 'number') {
|
||||||
}
|
this.#telemetryObject.openCount = openCount;
|
||||||
|
this.log(Verb.Started, 'telemetry');
|
||||||
async log(verb: Verb, noun: Noun, more?: Record<string, unknown>) {
|
} else {
|
||||||
if (!this.#started) {
|
this.log(Verb.Resumed, 'telemetry');
|
||||||
await this.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.#getCanLog()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const time = new Date().valueOf();
|
|
||||||
if (this.#telemetryObject.timeline === undefined) {
|
|
||||||
this.#telemetryObject.timeline = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#telemetryObject.timeline.push({ time, verb, noun, more });
|
|
||||||
}
|
|
||||||
|
|
||||||
error(name: string) {
|
|
||||||
if (this.#telemetryObject.errors === undefined) {
|
|
||||||
this.#telemetryObject.errors = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#telemetryObject.errors[name] ??= 0;
|
|
||||||
this.#telemetryObject.errors[name] += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
if (!this.started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(Verb.Stopped, 'telemetry');
|
||||||
this.#started = false;
|
this.#started = false;
|
||||||
|
this.#clear();
|
||||||
|
}
|
||||||
|
|
||||||
this.#telemetryObject.version = this.fyo.store.appVersion ?? '';
|
log(verb: Verb, noun: Noun, more?: Record<string, unknown>) {
|
||||||
this.#telemetryObject.closeTime = new Date().valueOf();
|
if (!this.#started) {
|
||||||
|
this.start().then(() => this.#sendBeacon(verb, noun, more));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#sendBeacon(verb, noun, more);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendBeacon(verb: Verb, noun: Noun, more?: Record<string, unknown>) {
|
||||||
|
if (!this.hasCreds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryData: Telemetry = this.#getTelemtryData(verb, noun, more);
|
||||||
const data = JSON.stringify({
|
const data = JSON.stringify({
|
||||||
token: this.#token,
|
token: this.#token,
|
||||||
telemetryData: this.#telemetryObject,
|
telemetryData,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#clear();
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.fyo.config.get(ConfigKeys.Telemetry) ===
|
|
||||||
TelemetrySetting.dontLogAnything
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigator.sendBeacon(this.#url, data);
|
navigator.sendBeacon(this.#url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
finalLogAndStop() {
|
|
||||||
this.log(Verb.Stopped, NounEnum.Telemetry);
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
async #postStart() {
|
|
||||||
await this.#setCount();
|
|
||||||
await this.#setCreds();
|
|
||||||
}
|
|
||||||
|
|
||||||
async #setCount() {
|
|
||||||
if (!this.#getCanLog()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#telemetryObject.counts = await getCounts(
|
|
||||||
this.#interestingDocs,
|
|
||||||
this.fyo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #setCreds() {
|
async #setCreds() {
|
||||||
if (this.hasCreds) {
|
if (this.hasCreds) {
|
||||||
return;
|
return;
|
||||||
@ -175,21 +123,31 @@ export class TelemetryManager {
|
|||||||
this.#token = token;
|
this.#token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
#getCanLog(): boolean {
|
#getTelemtryData(
|
||||||
const telemetrySetting = this.fyo.config.get(
|
verb: Verb,
|
||||||
ConfigKeys.Telemetry
|
noun: Noun,
|
||||||
) as string;
|
more?: Record<string, unknown>
|
||||||
return telemetrySetting === TelemetrySetting.allow;
|
): Telemetry {
|
||||||
|
return {
|
||||||
|
country: this.#telemetryObject.country!,
|
||||||
|
language: this.#telemetryObject.language!,
|
||||||
|
device: this.#telemetryObject.device!,
|
||||||
|
instance: this.#telemetryObject.instance!,
|
||||||
|
version: this.#telemetryObject.version!,
|
||||||
|
openCount: this.#telemetryObject.openCount!,
|
||||||
|
timestamp: DateTime.now().toMillis().toString(),
|
||||||
|
verb,
|
||||||
|
noun,
|
||||||
|
more,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#clear() {
|
#clear() {
|
||||||
// Delete only what varies
|
|
||||||
delete this.#telemetryObject.openTime;
|
|
||||||
delete this.#telemetryObject.closeTime;
|
|
||||||
delete this.#telemetryObject.errors;
|
|
||||||
delete this.#telemetryObject.counts;
|
|
||||||
delete this.#telemetryObject.timeline;
|
|
||||||
delete this.#telemetryObject.instanceId;
|
|
||||||
delete this.#telemetryObject.country;
|
delete this.#telemetryObject.country;
|
||||||
|
delete this.#telemetryObject.language;
|
||||||
|
delete this.#telemetryObject.device;
|
||||||
|
delete this.#telemetryObject.instance;
|
||||||
|
delete this.#telemetryObject.version;
|
||||||
|
delete this.#telemetryObject.openCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,33 @@
|
|||||||
export type AppVersion = string;
|
export type AppVersion = string;
|
||||||
export type UniqueId = string;
|
export type UniqueId = string;
|
||||||
export type Timestamp = number;
|
export type Timestamp = string;
|
||||||
|
|
||||||
export interface InteractionEvent {
|
|
||||||
time: Timestamp;
|
|
||||||
verb: Verb;
|
|
||||||
noun: Noun;
|
|
||||||
more?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Count = Record<string, number>;
|
|
||||||
export type Platform = 'Windows' | 'Mac' | 'Linux';
|
export type Platform = 'Windows' | 'Mac' | 'Linux';
|
||||||
|
|
||||||
export interface Telemetry {
|
|
||||||
deviceId: UniqueId;
|
|
||||||
instanceId: UniqueId;
|
|
||||||
openTime: Timestamp;
|
|
||||||
platform?: Platform;
|
|
||||||
closeTime: Timestamp;
|
|
||||||
timeline?: InteractionEvent[];
|
|
||||||
counts?: Count;
|
|
||||||
errors: Record<string, number>;
|
|
||||||
country: string;
|
|
||||||
language: string;
|
|
||||||
version: AppVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Verb {
|
export enum Verb {
|
||||||
Created = 'created',
|
Created = 'created',
|
||||||
Deleted = 'deleted',
|
Deleted = 'deleted',
|
||||||
Navigated = 'navigated',
|
Submitted = 'submitted',
|
||||||
|
Cancelled = 'cancelled',
|
||||||
Imported = 'imported',
|
Imported = 'imported',
|
||||||
Exported = 'exported',
|
Exported = 'exported',
|
||||||
Stopped = 'stopped',
|
Stopped = 'stopped',
|
||||||
Started = 'stopped',
|
Started = 'started',
|
||||||
|
Resumed = 'resumed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NounEnum {
|
export type Noun = string;
|
||||||
Route = 'route',
|
|
||||||
Telemetry = 'telemetry',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Noun = string | NounEnum;
|
export interface Telemetry {
|
||||||
|
device: UniqueId;
|
||||||
export enum TelemetrySetting {
|
instance: UniqueId;
|
||||||
allow = 'allow',
|
platform?: Platform;
|
||||||
dontLogUsage = 'dontLogUsage',
|
country: string;
|
||||||
dontLogAnything = 'dontLogAnything',
|
language: string;
|
||||||
|
version: AppVersion;
|
||||||
|
timestamp: Timestamp;
|
||||||
|
openCount: number;
|
||||||
|
verb: Verb;
|
||||||
|
noun: Noun;
|
||||||
|
more?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,12 @@
|
|||||||
"label": "Version",
|
"label": "Version",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"readOnly": true
|
"readOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "instanceId",
|
||||||
|
"label": "Instance Id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"readOnly": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
|
15
src/App.vue
15
src/App.vue
@ -29,20 +29,12 @@
|
|||||||
>
|
>
|
||||||
<div id="toast-target" />
|
<div id="toast-target" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Prompt to Set Telemetry -->
|
|
||||||
<TelemetryModal />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
import { ConfigKeys } from 'fyo/core/types';
|
||||||
import {
|
import { getSetupComplete, incrementOpenCount } from 'src/utils/misc';
|
||||||
getSetupComplete,
|
|
||||||
incrementOpenCount,
|
|
||||||
startTelemetry
|
|
||||||
} from 'src/utils/misc';
|
|
||||||
import TelemetryModal from './components/once/TelemetryModal.vue';
|
|
||||||
import WindowsTitleBar from './components/WindowsTitleBar.vue';
|
import WindowsTitleBar from './components/WindowsTitleBar.vue';
|
||||||
import { fyo, initializeInstance } from './initFyo';
|
import { fyo, initializeInstance } from './initFyo';
|
||||||
import DatabaseSelector from './pages/DatabaseSelector.vue';
|
import DatabaseSelector from './pages/DatabaseSelector.vue';
|
||||||
@ -65,7 +57,6 @@ export default {
|
|||||||
SetupWizard,
|
SetupWizard,
|
||||||
DatabaseSelector,
|
DatabaseSelector,
|
||||||
WindowsTitleBar,
|
WindowsTitleBar,
|
||||||
TelemetryModal,
|
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
fyo.telemetry.platform = this.platform;
|
fyo.telemetry.platform = this.platform;
|
||||||
@ -86,8 +77,8 @@ export default {
|
|||||||
async setDesk(filePath) {
|
async setDesk(filePath) {
|
||||||
this.activeScreen = 'Desk';
|
this.activeScreen = 'Desk';
|
||||||
await this.setDeskRoute();
|
await this.setDeskRoute();
|
||||||
await incrementOpenCount(filePath);
|
const openCount = await incrementOpenCount(filePath);
|
||||||
await startTelemetry();
|
await fyo.telemetry.start(openCount);
|
||||||
await checkForUpdates(false);
|
await checkForUpdates(false);
|
||||||
},
|
},
|
||||||
async fileSelected(filePath, isNew) {
|
async fileSelected(filePath, isNew) {
|
||||||
|
@ -1,135 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Modal :open-modal="shouldOpen" class="p-6 flex flex-col gap-3 text-gray-900">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<h1 class="font-bold text-md">{{ t`Set Anonymized Telemetry` }}</h1>
|
|
||||||
<button @click="shouldOpen = false">
|
|
||||||
<FeatherIcon name="x" class="w-5 h-5 text-gray-600" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p class="text-base mt-4">
|
|
||||||
{{ t`Hello there! 👋` }}
|
|
||||||
</p>
|
|
||||||
<p class="text-base">
|
|
||||||
{{
|
|
||||||
t`Frappe Books uses opt-in telemetry. This is the only way for us to know if we have any consistent
|
|
||||||
users. It will be really helpful if you switch it on, but we won't force you. 🙂`
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<p class="text-base mt-4">
|
|
||||||
{{ t`Please select an option:` }}
|
|
||||||
</p>
|
|
||||||
<FormControl
|
|
||||||
:df="df"
|
|
||||||
class="text-sm border rounded-md"
|
|
||||||
@change="
|
|
||||||
(v) => {
|
|
||||||
value = v;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:value="value"
|
|
||||||
/>
|
|
||||||
<p class="text-base text-gray-800">{{ description }}</p>
|
|
||||||
<div class="flex flex-row w-full justify-between items-center mt-12">
|
|
||||||
<HowTo
|
|
||||||
link="https://github.com/frappe/books/wiki/Anonymized-Opt-In-Telemetry"
|
|
||||||
class="text-sm hover:text-gray-900 text-gray-800 py-1 justify-between"
|
|
||||||
:icon="false"
|
|
||||||
>{{ t`Know More` }}</HowTo
|
|
||||||
>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
class="text-sm w-32"
|
|
||||||
type="primary"
|
|
||||||
:disabled="!isSet"
|
|
||||||
@click="saveClicked"
|
|
||||||
>{{ t`Save Option` }}</Button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
|
||||||
import { getTelemetryOptions } from 'fyo/telemetry/helpers';
|
|
||||||
import { TelemetrySetting } from 'fyo/telemetry/types';
|
|
||||||
import { fyo } from 'src/initFyo';
|
|
||||||
import Button from '../Button.vue';
|
|
||||||
import FormControl from '../Controls/FormControl.vue';
|
|
||||||
import FeatherIcon from '../FeatherIcon.vue';
|
|
||||||
import HowTo from '../HowTo.vue';
|
|
||||||
import Modal from '../Modal.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { Modal, FormControl, Button, HowTo, FeatherIcon },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
shouldOpen: false,
|
|
||||||
value: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
df() {
|
|
||||||
const telemetryOptions = getTelemetryOptions();
|
|
||||||
return {
|
|
||||||
fieldname: 'anonymizedTelemetry',
|
|
||||||
label: this.t`Anonymized Telemetry`,
|
|
||||||
fieldtype: 'Select',
|
|
||||||
options: Object.keys(telemetryOptions),
|
|
||||||
map: telemetryOptions,
|
|
||||||
default: 'allow',
|
|
||||||
description: this
|
|
||||||
.t`Send anonymized usage data and error reports to help improve the product.`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
description() {
|
|
||||||
if (!this.isSet) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
[TelemetrySetting.allow]: this
|
|
||||||
.t`Enables telemetry. Includes usage patterns.`,
|
|
||||||
[TelemetrySetting.dontLogUsage]: this
|
|
||||||
.t`Enables telemetry. Does not include usage patterns.`,
|
|
||||||
[TelemetrySetting.dontLogAnything]: this
|
|
||||||
.t`Disables telemetry. No data will be collected, you are completely invisble to us.`,
|
|
||||||
}[this.value];
|
|
||||||
},
|
|
||||||
isSet() {
|
|
||||||
return this.getIsSet(this.value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
saveClicked() {
|
|
||||||
if (this.value === TelemetrySetting.dontLogUsage) {
|
|
||||||
telemetry.finalLogAndStop();
|
|
||||||
} else {
|
|
||||||
telemetry.log(Verb.Started, NounEnum.Telemetry);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.set(ConfigKeys.Telemetry, this.value);
|
|
||||||
this.shouldOpen = false;
|
|
||||||
},
|
|
||||||
getIsSet(value) {
|
|
||||||
return [
|
|
||||||
TelemetrySetting.allow,
|
|
||||||
TelemetrySetting.dontLogAnything,
|
|
||||||
TelemetrySetting.dontLogUsage,
|
|
||||||
].includes(value);
|
|
||||||
},
|
|
||||||
|
|
||||||
setOpen(telemetry) {
|
|
||||||
const openCount = fyo.config
|
|
||||||
.get(ConfigKeys.Files)
|
|
||||||
.map((f) => f.openCount)
|
|
||||||
.reduce((a, b) => (a ?? 0) + (b ?? 0));
|
|
||||||
this.shouldOpen = !this.getIsSet(telemetry) && openCount >= 4;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const telemetry = fyo.config.get(ConfigKeys.Telemetry);
|
|
||||||
this.setOpen(telemetry);
|
|
||||||
this.value = telemetry;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,9 +1,11 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { t } from 'fyo';
|
import { t } from 'fyo';
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { TelemetrySetting } from 'fyo/telemetry/types';
|
import {
|
||||||
import { MandatoryError, ValidationError } from 'fyo/utils/errors';
|
MandatoryError,
|
||||||
|
NotFoundError,
|
||||||
|
ValidationError,
|
||||||
|
} from 'fyo/utils/errors';
|
||||||
import { ErrorLog } from 'fyo/utils/types';
|
import { ErrorLog } from 'fyo/utils/types';
|
||||||
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
||||||
import { fyo } from './initFyo';
|
import { fyo } from './initFyo';
|
||||||
@ -11,13 +13,8 @@ import { getErrorMessage } from './utils';
|
|||||||
import { ToastOptions } from './utils/types';
|
import { ToastOptions } from './utils/types';
|
||||||
import { showMessageDialog, showToast } from './utils/ui';
|
import { showMessageDialog, showToast } from './utils/ui';
|
||||||
|
|
||||||
function getCanLog(): boolean {
|
|
||||||
const telemetrySetting = fyo.config.get(ConfigKeys.Telemetry);
|
|
||||||
return telemetrySetting !== TelemetrySetting.dontLogAnything;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldNotStore(error: Error) {
|
function shouldNotStore(error: Error) {
|
||||||
return [MandatoryError, ValidationError].some(
|
return [MandatoryError, ValidationError, NotFoundError].some(
|
||||||
(errorClass) => error instanceof errorClass
|
(errorClass) => error instanceof errorClass
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -43,23 +40,14 @@ async function reportError(errorLogObj: ErrorLog, cb?: Function) {
|
|||||||
cb?.();
|
cb?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getToastProps(errorLogObj: ErrorLog, canLog: boolean, cb?: Function) {
|
function getToastProps(errorLogObj: ErrorLog, cb?: Function) {
|
||||||
const props: ToastOptions = {
|
const props: ToastOptions = {
|
||||||
message: errorLogObj.name ?? t`Error`,
|
message: errorLogObj.name ?? t`Error`,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
actionText: t`Report Error`,
|
||||||
|
action: () => reportIssue(errorLogObj),
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
if (!canLog) {
|
|
||||||
Object.assign(props, {
|
|
||||||
actionText: t`Report Error`,
|
|
||||||
action: () => {
|
|
||||||
reportIssue(errorLogObj);
|
|
||||||
reportError(errorLogObj, cb);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,13 +64,12 @@ export function getErrorLogObject(
|
|||||||
return errorLogObj;
|
return errorLogObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleError(
|
export async function handleError(
|
||||||
shouldLog: boolean,
|
shouldLog: boolean,
|
||||||
error: Error,
|
error: Error,
|
||||||
more?: Record<string, unknown>,
|
more?: Record<string, unknown>,
|
||||||
cb?: Function
|
cb?: Function
|
||||||
) {
|
) {
|
||||||
fyo.telemetry.error(error.name);
|
|
||||||
if (shouldLog) {
|
if (shouldLog) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@ -93,17 +80,14 @@ export function handleError(
|
|||||||
|
|
||||||
const errorLogObj = getErrorLogObject(error, more ?? {});
|
const errorLogObj = getErrorLogObject(error, more ?? {});
|
||||||
|
|
||||||
const canLog = getCanLog();
|
await reportError(errorLogObj, cb);
|
||||||
if (canLog) {
|
const toastProps = getToastProps(errorLogObj, cb);
|
||||||
reportError(errorLogObj, cb);
|
await showToast(toastProps);
|
||||||
} else {
|
|
||||||
showToast(getToastProps(errorLogObj, canLog, cb));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleErrorWithDialog(error: Error, doc?: Doc) {
|
export async function handleErrorWithDialog(error: Error, doc?: Doc) {
|
||||||
const errorMessage = getErrorMessage(error, doc);
|
const errorMessage = getErrorMessage(error, doc);
|
||||||
handleError(false, error, { errorMessage, doc });
|
await handleError(false, error, { errorMessage, doc });
|
||||||
|
|
||||||
const name = error.name ?? t`Error`;
|
const name = error.name ?? t`Error`;
|
||||||
await showMessageDialog({ message: name, detail: errorMessage });
|
await showMessageDialog({ message: name, detail: errorMessage });
|
||||||
@ -125,7 +109,7 @@ export function getErrorHandled(func: Function) {
|
|||||||
try {
|
try {
|
||||||
return await func(...args);
|
return await func(...args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(false, error as Error, {
|
await handleError(false, error as Error, {
|
||||||
functionName: func.name,
|
functionName: func.name,
|
||||||
functionArgs: args,
|
functionArgs: args,
|
||||||
});
|
});
|
||||||
@ -143,9 +127,9 @@ export function getErrorHandledSync(func: Function) {
|
|||||||
handleError(false, error as Error, {
|
handleError(false, error as Error, {
|
||||||
functionName: func.name,
|
functionName: func.name,
|
||||||
functionArgs: args,
|
functionArgs: args,
|
||||||
|
}).then(() => {
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { getRegionalModels, models } from 'models';
|
import { getRegionalModels, models } from 'models';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { getValueMapFromList } from 'utils';
|
import { getRandomString, getValueMapFromList } from 'utils';
|
||||||
|
|
||||||
export const fyo = new Fyo({ isTest: false, isElectron: true });
|
export const fyo = new Fyo({ isTest: false, isElectron: true });
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ export async function initializeInstance(
|
|||||||
await setSingles(fyo);
|
await setSingles(fyo);
|
||||||
await setCreds(fyo);
|
await setCreds(fyo);
|
||||||
await setVersion(fyo);
|
await setVersion(fyo);
|
||||||
|
await setInstanceId(fyo);
|
||||||
await setCurrencySymbols(fyo);
|
await setCurrencySymbols(fyo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +65,13 @@ async function setVersion(fyo: Fyo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setInstanceId(fyo: Fyo) {
|
||||||
|
const systemSettings = await fyo.doc.getSingle(ModelNameEnum.SystemSettings);
|
||||||
|
if (!systemSettings.instanceId) {
|
||||||
|
await systemSettings.setAndSync('instanceId', getRandomString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function setCurrencySymbols(fyo: Fyo) {
|
async function setCurrencySymbols(fyo: Fyo) {
|
||||||
const currencies = (await fyo.db.getAll(ModelNameEnum.Currency, {
|
const currencies = (await fyo.db.getAll(ModelNameEnum.Currency, {
|
||||||
fields: ['name', 'symbol'],
|
fields: ['name', 'symbol'],
|
||||||
|
@ -119,21 +119,38 @@
|
|||||||
|
|
||||||
<!-- Language Selector -->
|
<!-- Language Selector -->
|
||||||
<div
|
<div
|
||||||
class="w-full flex justify-between items-center absolute px-6 py-6"
|
class="
|
||||||
|
w-full
|
||||||
|
flex
|
||||||
|
justify-between
|
||||||
|
items-center
|
||||||
|
absolute
|
||||||
|
px-6
|
||||||
|
py-6
|
||||||
|
text-gray-900
|
||||||
|
"
|
||||||
style="top: 100%; transform: translateY(-100%)"
|
style="top: 100%; transform: translateY(-100%)"
|
||||||
>
|
>
|
||||||
<Button
|
|
||||||
class="text-sm w-40"
|
|
||||||
@click="createDemo"
|
|
||||||
:disabled="creatingDemo"
|
|
||||||
>{{ creatingDemo ? t`Please Wait` : t`Create Demo` }}</Button
|
|
||||||
>
|
|
||||||
|
|
||||||
<LanguageSelector
|
<LanguageSelector
|
||||||
v-show="!creatingDemo"
|
v-show="!creatingDemo"
|
||||||
class="w-40 bg-gray-100 rounded-md"
|
class="text-sm w-40 bg-gray-100 rounded-md"
|
||||||
input-class="text-sm bg-transparent"
|
input-class="py-1.5 bg-transparent"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
class="
|
||||||
|
text-sm
|
||||||
|
bg-gray-100
|
||||||
|
hover:bg-gray-200
|
||||||
|
rounded-md
|
||||||
|
px-4
|
||||||
|
py-1.5
|
||||||
|
w-40
|
||||||
|
"
|
||||||
|
@click="createDemo"
|
||||||
|
:disabled="creatingDemo"
|
||||||
|
>
|
||||||
|
{{ creatingDemo ? t`Please Wait` : t`Create Demo` }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Loading
|
<Loading
|
||||||
@ -153,7 +170,6 @@ import { t } from 'fyo';
|
|||||||
import { ConfigKeys } from 'fyo/core/types';
|
import { ConfigKeys } from 'fyo/core/types';
|
||||||
import { addNewFile } from 'fyo/telemetry/helpers';
|
import { addNewFile } from 'fyo/telemetry/helpers';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import Button from 'src/components/Button.vue';
|
|
||||||
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
|
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
|
||||||
import FeatherIcon from 'src/components/FeatherIcon.vue';
|
import FeatherIcon from 'src/components/FeatherIcon.vue';
|
||||||
import Loading from 'src/components/Loading.vue';
|
import Loading from 'src/components/Loading.vue';
|
||||||
@ -217,7 +233,7 @@ export default {
|
|||||||
this.creatingDemo = true;
|
this.creatingDemo = true;
|
||||||
const baseCount = fyo.store.isDevelopment ? 1000 : 150;
|
const baseCount = fyo.store.isDevelopment ? 1000 : 150;
|
||||||
|
|
||||||
const companyName = await setupDummyInstance(
|
const { companyName, instanceId } = await setupDummyInstance(
|
||||||
filePath,
|
filePath,
|
||||||
fyo,
|
fyo,
|
||||||
1,
|
1,
|
||||||
@ -230,9 +246,10 @@ export default {
|
|||||||
|
|
||||||
addNewFile(
|
addNewFile(
|
||||||
companyName,
|
companyName,
|
||||||
fyo,
|
filePath,
|
||||||
fyo.config.get(ConfigKeys.Files, []),
|
instanceId,
|
||||||
filePath
|
fyo.config.get(ConfigKeys.Files),
|
||||||
|
fyo
|
||||||
);
|
);
|
||||||
|
|
||||||
fyo.purgeCache();
|
fyo.purgeCache();
|
||||||
@ -292,7 +309,6 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LanguageSelector,
|
LanguageSelector,
|
||||||
WindowControls,
|
WindowControls,
|
||||||
Button,
|
|
||||||
Loading,
|
Loading,
|
||||||
FeatherIcon,
|
FeatherIcon,
|
||||||
},
|
},
|
||||||
|
@ -8,26 +8,22 @@
|
|||||||
:emit-change="true"
|
:emit-change="true"
|
||||||
@change="forwardChangeEvent"
|
@change="forwardChangeEvent"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row justify-between items-center w-full">
|
<div
|
||||||
<div class="flex items-center">
|
class="flex flex-row justify-between items-center w-full text-gray-900"
|
||||||
<FormControl
|
>
|
||||||
:df="df"
|
<LanguageSelector
|
||||||
:value="telemetry"
|
class="text-sm w-40 bg-gray-100 rounded-md"
|
||||||
@change="setValue"
|
input-class="py-1.5 bg-transparent"
|
||||||
class="text-sm py-0 w-44"
|
/>
|
||||||
:label-right="false"
|
|
||||||
/>
|
|
||||||
<div class="border-r h-6 mx-2" />
|
|
||||||
<LanguageSelector class="text-sm w-44" input-class="py-2" />
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
class="
|
class="
|
||||||
text-gray-900 text-sm
|
text-sm
|
||||||
bg-gray-100
|
bg-gray-100
|
||||||
hover:bg-gray-200
|
hover:bg-gray-200
|
||||||
rounded-md
|
rounded-md
|
||||||
px-4
|
px-4
|
||||||
py-1.5
|
py-1.5
|
||||||
|
w-40
|
||||||
"
|
"
|
||||||
@click="checkForUpdates(true)"
|
@click="checkForUpdates(true)"
|
||||||
>
|
>
|
||||||
@ -39,10 +35,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
import { ConfigKeys } from 'fyo/core/types';
|
||||||
import { getTelemetryOptions } from 'fyo/telemetry/helpers';
|
|
||||||
import { NounEnum, TelemetrySetting, Verb } from 'fyo/telemetry/types';
|
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
|
||||||
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
|
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
|
||||||
import TwoColumnForm from 'src/components/TwoColumnForm';
|
import TwoColumnForm from 'src/components/TwoColumnForm';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
@ -52,7 +45,6 @@ import { getCountryInfo } from 'utils/misc';
|
|||||||
export default {
|
export default {
|
||||||
name: 'TabSystem',
|
name: 'TabSystem',
|
||||||
components: {
|
components: {
|
||||||
FormControl,
|
|
||||||
TwoColumnForm,
|
TwoColumnForm,
|
||||||
LanguageSelector,
|
LanguageSelector,
|
||||||
},
|
},
|
||||||
@ -67,22 +59,9 @@ export default {
|
|||||||
this.doc = fyo.singles.SystemSettings;
|
this.doc = fyo.singles.SystemSettings;
|
||||||
this.companyName = fyo.singles.AccountingSettings.companyName;
|
this.companyName = fyo.singles.AccountingSettings.companyName;
|
||||||
this.telemetry = fyo.config.get(ConfigKeys.Telemetry);
|
this.telemetry = fyo.config.get(ConfigKeys.Telemetry);
|
||||||
window.gci = getCountryInfo
|
window.gci = getCountryInfo;
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
df() {
|
|
||||||
const telemetryOptions = getTelemetryOptions();
|
|
||||||
return {
|
|
||||||
fieldname: 'anonymizedTelemetry',
|
|
||||||
label: this.t`Anonymized Telemetry`,
|
|
||||||
fieldtype: 'Select',
|
|
||||||
options: Object.keys(telemetryOptions),
|
|
||||||
map: telemetryOptions,
|
|
||||||
default: 'allow',
|
|
||||||
description: this
|
|
||||||
.t`Send anonymized usage data and error reports to help improve the product.`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
fields() {
|
fields() {
|
||||||
return fyo.schemaMap.SystemSettings.quickEditFields.map((f) =>
|
return fyo.schemaMap.SystemSettings.quickEditFields.map((f) =>
|
||||||
fyo.getField(ModelNameEnum.SystemSettings, f)
|
fyo.getField(ModelNameEnum.SystemSettings, f)
|
||||||
@ -91,16 +70,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
setValue(value) {
|
|
||||||
this.telemetry = value;
|
|
||||||
if (value === TelemetrySetting.dontLogAnything) {
|
|
||||||
fyo.telemetry.finalLogAndStop();
|
|
||||||
} else {
|
|
||||||
fyo.telemetry.log(Verb.Started, NounEnum.Telemetry);
|
|
||||||
}
|
|
||||||
|
|
||||||
fyo.config.set(ConfigKeys.Telemetry, value);
|
|
||||||
},
|
|
||||||
forwardChangeEvent(...args) {
|
forwardChangeEvent(...args) {
|
||||||
this.$emit('change', ...args);
|
this.$emit('change', ...args);
|
||||||
},
|
},
|
||||||
|
@ -81,7 +81,6 @@ function setErrorHandlers(app: VueApp) {
|
|||||||
const { fullPath, params } = vm.$route;
|
const { fullPath, params } = vm.$route;
|
||||||
more.fullPath = fullPath;
|
more.fullPath = fullPath;
|
||||||
more.params = stringifyCircular(params ?? {});
|
more.params = stringifyCircular(params ?? {});
|
||||||
more.data = stringifyCircular(vm.$data ?? {}, true, true);
|
|
||||||
more.props = stringifyCircular(vm.$props ?? {}, true, true);
|
more.props = stringifyCircular(vm.$props ?? {}, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { handleError } from 'src/errorHandling';
|
import { handleError } from 'src/errorHandling';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { startTelemetry } from 'src/utils/misc';
|
|
||||||
import { showToast } from 'src/utils/ui';
|
import { showToast } from 'src/utils/ui';
|
||||||
import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages';
|
import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages';
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ export default function registerIpcRendererListeners() {
|
|||||||
document.addEventListener('visibilitychange', function () {
|
document.addEventListener('visibilitychange', function () {
|
||||||
const { visibilityState } = document;
|
const { visibilityState } = document;
|
||||||
if (visibilityState === 'visible' && !fyo.telemetry.started) {
|
if (visibilityState === 'visible' && !fyo.telemetry.started) {
|
||||||
startTelemetry();
|
fyo.telemetry.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visibilityState !== 'hidden') {
|
if (visibilityState !== 'hidden') {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { NounEnum, Verb } from 'fyo/telemetry/types';
|
|
||||||
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
|
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
|
||||||
import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
|
import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
|
||||||
import DataImport from 'src/pages/DataImport.vue';
|
import DataImport from 'src/pages/DataImport.vue';
|
||||||
@ -11,7 +10,6 @@ import QuickEditForm from 'src/pages/QuickEditForm.vue';
|
|||||||
import Report from 'src/pages/Report.vue';
|
import Report from 'src/pages/Report.vue';
|
||||||
import Settings from 'src/pages/Settings/Settings.vue';
|
import Settings from 'src/pages/Settings/Settings.vue';
|
||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import { fyo } from './initFyo';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -120,26 +118,4 @@ const routes: RouteRecordRaw[] = [
|
|||||||
|
|
||||||
const router = createRouter({ routes, history: createWebHistory() });
|
const router = createRouter({ routes, history: createWebHistory() });
|
||||||
|
|
||||||
function removeDetails(path: string) {
|
|
||||||
if (!path) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = path.match(/edit=1/);
|
|
||||||
if (!match) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.slice(0, match.index! + 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
router.afterEach((to, from) => {
|
|
||||||
const more = {
|
|
||||||
from: removeDetails(from.fullPath),
|
|
||||||
to: removeDetails(to.fullPath),
|
|
||||||
};
|
|
||||||
|
|
||||||
fyo.telemetry.log(Verb.Navigated, NounEnum.Route, more);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -2,15 +2,16 @@ import { Fyo } from 'fyo';
|
|||||||
import { ConfigFile, DocValueMap } from 'fyo/core/types';
|
import { ConfigFile, DocValueMap } from 'fyo/core/types';
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { createNumberSeries } from 'fyo/model/naming';
|
import { createNumberSeries } from 'fyo/model/naming';
|
||||||
import { getId } from 'fyo/telemetry/helpers';
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_CURRENCY,
|
DEFAULT_CURRENCY,
|
||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
DEFAULT_SERIES_START,
|
DEFAULT_SERIES_START,
|
||||||
} from 'fyo/utils/consts';
|
} from 'fyo/utils/consts';
|
||||||
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { initializeInstance } from 'src/initFyo';
|
import { initializeInstance } from 'src/initFyo';
|
||||||
import { createRegionalRecords } from 'src/regional';
|
import { createRegionalRecords } from 'src/regional';
|
||||||
|
import { getRandomString } from 'utils';
|
||||||
import { getCountryCodeFromCountry, getCountryInfo } from 'utils/misc';
|
import { getCountryCodeFromCountry, getCountryInfo } from 'utils/misc';
|
||||||
import { CountryInfo } from 'utils/types';
|
import { CountryInfo } from 'utils/types';
|
||||||
import { CreateCOA } from './createCOA';
|
import { CreateCOA } from './createCOA';
|
||||||
@ -93,10 +94,12 @@ async function updateSystemSettings(
|
|||||||
const locale = countryOptions.locale ?? DEFAULT_LOCALE;
|
const locale = countryOptions.locale ?? DEFAULT_LOCALE;
|
||||||
const countryCode = getCountryCodeFromCountry(country);
|
const countryCode = getCountryCodeFromCountry(country);
|
||||||
const systemSettings = await fyo.doc.getSingle('SystemSettings');
|
const systemSettings = await fyo.doc.getSingle('SystemSettings');
|
||||||
|
const instanceId = getRandomString();
|
||||||
|
|
||||||
systemSettings.setAndSync({
|
systemSettings.setAndSync({
|
||||||
locale,
|
locale,
|
||||||
currency,
|
currency,
|
||||||
|
instanceId,
|
||||||
countryCode,
|
countryCode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -156,18 +159,22 @@ async function createAccountRecords(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function completeSetup(companyName: string, fyo: Fyo) {
|
async function completeSetup(companyName: string, fyo: Fyo) {
|
||||||
updateInitializationConfig(companyName, fyo);
|
await updateInitializationConfig(companyName, fyo);
|
||||||
await fyo.singles.AccountingSettings!.setAndSync('setupComplete', true);
|
await fyo.singles.AccountingSettings!.setAndSync('setupComplete', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateInitializationConfig(companyName: string, fyo: Fyo) {
|
async function updateInitializationConfig(companyName: string, fyo: Fyo) {
|
||||||
|
const instanceId = (await fyo.getValue(
|
||||||
|
ModelNameEnum.SystemSettings,
|
||||||
|
'instanceId'
|
||||||
|
)) as string;
|
||||||
const dbPath = fyo.db.dbPath;
|
const dbPath = fyo.db.dbPath;
|
||||||
const files = fyo.config.get('files', []) as ConfigFile[];
|
const files = fyo.config.get('files', []) as ConfigFile[];
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
if (file.dbPath === dbPath) {
|
if (file.dbPath === dbPath) {
|
||||||
file.companyName = companyName;
|
file.companyName = companyName;
|
||||||
file.id = getId();
|
file.id = instanceId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,6 +71,8 @@ export async function incrementOpenCount(dbPath: string) {
|
|||||||
ModelNameEnum.AccountingSettings,
|
ModelNameEnum.AccountingSettings,
|
||||||
'companyName'
|
'companyName'
|
||||||
)) as string;
|
)) as string;
|
||||||
|
|
||||||
|
let openCount = 0;
|
||||||
const files = fyo.config.get(ConfigKeys.Files) as ConfigFile[];
|
const files = fyo.config.get(ConfigKeys.Files) as ConfigFile[];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.companyName !== companyName || file.dbPath !== dbPath) {
|
if (file.companyName !== companyName || file.dbPath !== dbPath) {
|
||||||
@ -79,20 +81,10 @@ export async function incrementOpenCount(dbPath: string) {
|
|||||||
|
|
||||||
file.openCount ??= 0;
|
file.openCount ??= 0;
|
||||||
file.openCount += 1;
|
file.openCount += 1;
|
||||||
|
openCount = file.openCount;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fyo.config.set(ConfigKeys.Files, files);
|
fyo.config.set(ConfigKeys.Files, files);
|
||||||
}
|
return openCount;
|
||||||
|
|
||||||
export async function startTelemetry() {
|
|
||||||
fyo.telemetry.interestingDocs = [
|
|
||||||
ModelNameEnum.Payment,
|
|
||||||
ModelNameEnum.SalesInvoice,
|
|
||||||
ModelNameEnum.PurchaseInvoice,
|
|
||||||
ModelNameEnum.JournalEntry,
|
|
||||||
ModelNameEnum.Party,
|
|
||||||
ModelNameEnum.Item,
|
|
||||||
];
|
|
||||||
await fyo.telemetry.start();
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user