2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

incr: simplify telemetry

- it's not opt-in anymore cause my job deps on it
This commit is contained in:
18alantom 2022-05-23 13:39:07 +05:30
parent deefcd08f9
commit 2c596b11c8
18 changed files with 224 additions and 491 deletions

View File

@ -48,7 +48,12 @@ export async function setupDummyInstance(
notifier?.(fyo.t`Creating Items and Parties`, -1);
await generateStaticEntries(fyo);
await generateDynamicEntries(fyo, years, baseCount, notifier);
return options.companyName;
const instanceId = (await fyo.getValue(
ModelNameEnum.SystemSettings,
'instanceId'
)) as string;
return { companyName: options.companyName, instanceId };
}
/**

View File

@ -34,7 +34,6 @@ export enum ConfigKeys {
LastSelectedFilePath = 'lastSelectedFilePath',
Language = 'language',
DeviceId = 'deviceId',
Telemetry = 'telemetry',
}
export interface ConfigFile {

View File

@ -678,6 +678,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
await this.setAndSync('submitted', true);
await this.trigger('afterSubmit');
this.fyo.telemetry.log(Verb.Submitted, this.schemaName);
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.trigger('afterCancel');
this.fyo.telemetry.log(Verb.Cancelled, this.schemaName);
this.fyo.doc.observer.trigger(`cancel:${this.schemaName}`, this.name);
}

View File

@ -1,18 +1,9 @@
import { Fyo } from 'fyo';
import { ConfigFile, ConfigKeys } from 'fyo/core/types';
import { DEFAULT_COUNTRY_CODE } from 'fyo/utils/consts';
import { t } from 'fyo/utils/translation';
import { Count, TelemetrySetting, 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;
}
import { ModelNameEnum } from 'models/types';
import { getRandomString } from 'utils';
import { UniqueId } from './types';
export function getCountry(fyo: Fyo): string {
return (
@ -24,27 +15,10 @@ export function getLanguage(fyo: Fyo): 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 {
let deviceId = fyo.config.get(ConfigKeys.DeviceId) as string | undefined;
if (deviceId === undefined) {
deviceId = getId();
deviceId = getRandomString();
fyo.config.set(ConfigKeys.DeviceId, deviceId);
}
@ -53,69 +27,69 @@ export function getDeviceId(fyo: Fyo): UniqueId {
export function getInstanceId(fyo: Fyo): UniqueId {
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;
if (companyName === undefined) {
return '';
}
const file = files.find((f) => f.companyName === companyName);
let file = files.find((f) => f.id === instanceId);
if (file === undefined) {
return addNewFile(companyName, fyo, files);
file = addNewConfigFile(companyName, dbPath, instanceId, files, fyo);
}
if (file.id === undefined) {
return setInstanceId(companyName, files, fyo);
if (!file.id) {
setIdOnConfigFile(instanceId, companyName, dbPath, files, fyo);
}
return file.id;
return instanceId;
}
export function addNewFile(
export function addNewConfigFile(
companyName: string,
fyo: Fyo,
files?: ConfigFile[],
dbPath?: string
): UniqueId {
files ??= fyo.config.get(ConfigKeys.Files, []) as ConfigFile[];
dbPath ??= fyo.config.get(ConfigKeys.LastSelectedFilePath, '') as string;
dbPath: string,
instanceId: string,
files: ConfigFile[],
fyo: Fyo
): ConfigFile {
const newFile: ConfigFile = {
companyName,
dbPath,
id: getId(),
id: instanceId,
openCount: 0,
};
files.push(newFile);
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,
dbPath: string,
files: ConfigFile[],
fyo: Fyo
): UniqueId {
let id = '';
) {
for (const file of files) {
if (file.id) {
if (file.companyName !== companyName || file.dbPath !== dbPath) {
continue;
}
file.id = getId();
if (file.companyName === companyName) {
id = file.id;
}
file.id = instanceId;
}
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`,
});

View File

@ -1,53 +1,38 @@
import { Fyo } from 'fyo';
import { ConfigKeys } from 'fyo/core/types';
import { cloneDeep } from 'lodash';
import { DateTime } from 'luxon';
import {
getCountry,
getCounts,
getDeviceId,
getInstanceId,
getLanguage,
getVersion,
} from './helpers';
import {
Noun,
NounEnum,
Platform,
Telemetry,
TelemetrySetting,
Verb,
} from './types';
import { Noun, Platform, Telemetry, Verb } from './types';
/**
* # Telemetry
* Used to check if people are using Books or not. All logging
* happens using navigator.sendBeacon
*
* ## `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:
* 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
* then comes back later.
* 3. When `log` is called if not initialized.
* 3. When `log` is called, but telemetry wasn't initialized.
*
* ## `log`
* Used to make entries in the `timeline` which happens only if telmetry
* is set to 'Allow Telemetry`
*
* ## `error`
* Called in errorHandling.ts and maintains a count of errors that were
* thrown during usage.
* Used to log activity.
*
* ## `stop`
* This is to be called when a session is being stopped. It's called on two events
* 1. When the db is being changed.
* 2. When the visiblity has changed which happens when either the app is being shut or
* 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 {
@ -55,7 +40,6 @@ export class TelemetryManager {
#token: string = '';
#started = false;
#telemetryObject: Partial<Telemetry> = {};
#interestingDocs: string[] = [];
fyo: Fyo;
constructor(fyo: Fyo) {
@ -66,10 +50,6 @@ export class TelemetryManager {
this.#telemetryObject.platform ||= value;
}
set interestingDocs(schemaNames: string[]) {
this.#interestingDocs = schemaNames;
}
get hasCreds() {
return !!this.#url && !!this.#token;
}
@ -82,89 +62,57 @@ export class TelemetryManager {
return cloneDeep(this.#telemetryObject);
}
async start() {
async start(openCount?: number) {
this.#telemetryObject.country ||= getCountry(this.fyo);
this.#telemetryObject.language ??= getLanguage(this.fyo);
this.#telemetryObject.deviceId ||= getDeviceId(this.fyo);
this.#telemetryObject.instanceId ||= getInstanceId(this.fyo);
this.#telemetryObject.openTime ||= new Date().valueOf();
this.#telemetryObject.timeline ??= [];
this.#telemetryObject.errors ??= {};
this.#telemetryObject.counts ??= {};
this.#telemetryObject.device ||= getDeviceId(this.fyo);
this.#telemetryObject.instance ||= getInstanceId(this.fyo);
this.#telemetryObject.version ||= await getVersion(this.fyo);
this.#started = true;
await this.#setCreds();
await this.#postStart();
}
async log(verb: Verb, noun: Noun, more?: Record<string, unknown>) {
if (!this.#started) {
await this.start();
if (typeof openCount === 'number') {
this.#telemetryObject.openCount = openCount;
this.log(Verb.Started, 'telemetry');
} else {
this.log(Verb.Resumed, 'telemetry');
}
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() {
if (!this.started) {
return;
}
this.log(Verb.Stopped, 'telemetry');
this.#started = false;
this.#clear();
}
this.#telemetryObject.version = this.fyo.store.appVersion ?? '';
this.#telemetryObject.closeTime = new Date().valueOf();
log(verb: Verb, noun: Noun, more?: Record<string, unknown>) {
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({
token: this.#token,
telemetryData: this.#telemetryObject,
telemetryData,
});
this.#clear();
if (
this.fyo.config.get(ConfigKeys.Telemetry) ===
TelemetrySetting.dontLogAnything
) {
return;
}
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() {
if (this.hasCreds) {
return;
@ -175,21 +123,31 @@ export class TelemetryManager {
this.#token = token;
}
#getCanLog(): boolean {
const telemetrySetting = this.fyo.config.get(
ConfigKeys.Telemetry
) as string;
return telemetrySetting === TelemetrySetting.allow;
#getTelemtryData(
verb: Verb,
noun: Noun,
more?: Record<string, unknown>
): 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() {
// 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.language;
delete this.#telemetryObject.device;
delete this.#telemetryObject.instance;
delete this.#telemetryObject.version;
delete this.#telemetryObject.openCount;
}
}

View File

@ -1,50 +1,33 @@
export type AppVersion = 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 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 {
Created = 'created',
Deleted = 'deleted',
Navigated = 'navigated',
Submitted = 'submitted',
Cancelled = 'cancelled',
Imported = 'imported',
Exported = 'exported',
Stopped = 'stopped',
Started = 'stopped',
Started = 'started',
Resumed = 'resumed',
}
export enum NounEnum {
Route = 'route',
Telemetry = 'telemetry',
}
export type Noun = string;
export type Noun = string | NounEnum;
export enum TelemetrySetting {
allow = 'allow',
dontLogUsage = 'dontLogUsage',
dontLogAnything = 'dontLogAnything',
export interface Telemetry {
device: UniqueId;
instance: UniqueId;
platform?: Platform;
country: string;
language: string;
version: AppVersion;
timestamp: Timestamp;
openCount: number;
verb: Verb;
noun: Noun;
more?: Record<string, unknown>
}

View File

@ -95,6 +95,12 @@
"label": "Version",
"fieldtype": "Data",
"readOnly": true
},
{
"fieldname": "instanceId",
"label": "Instance Id",
"fieldtype": "Data",
"readOnly": true
}
],
"quickEditFields": [

View File

@ -29,20 +29,12 @@
>
<div id="toast-target" />
</div>
<!-- Prompt to Set Telemetry -->
<TelemetryModal />
</div>
</template>
<script>
import { ConfigKeys } from 'fyo/core/types';
import {
getSetupComplete,
incrementOpenCount,
startTelemetry
} from 'src/utils/misc';
import TelemetryModal from './components/once/TelemetryModal.vue';
import { getSetupComplete, incrementOpenCount } from 'src/utils/misc';
import WindowsTitleBar from './components/WindowsTitleBar.vue';
import { fyo, initializeInstance } from './initFyo';
import DatabaseSelector from './pages/DatabaseSelector.vue';
@ -65,7 +57,6 @@ export default {
SetupWizard,
DatabaseSelector,
WindowsTitleBar,
TelemetryModal,
},
async mounted() {
fyo.telemetry.platform = this.platform;
@ -86,8 +77,8 @@ export default {
async setDesk(filePath) {
this.activeScreen = 'Desk';
await this.setDeskRoute();
await incrementOpenCount(filePath);
await startTelemetry();
const openCount = await incrementOpenCount(filePath);
await fyo.telemetry.start(openCount);
await checkForUpdates(false);
},
async fileSelected(filePath, isNew) {

View File

@ -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>

View File

@ -1,9 +1,11 @@
import { ipcRenderer } from 'electron';
import { t } from 'fyo';
import { ConfigKeys } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { TelemetrySetting } from 'fyo/telemetry/types';
import { MandatoryError, ValidationError } from 'fyo/utils/errors';
import {
MandatoryError,
NotFoundError,
ValidationError,
} from 'fyo/utils/errors';
import { ErrorLog } from 'fyo/utils/types';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import { fyo } from './initFyo';
@ -11,13 +13,8 @@ import { getErrorMessage } from './utils';
import { ToastOptions } from './utils/types';
import { showMessageDialog, showToast } from './utils/ui';
function getCanLog(): boolean {
const telemetrySetting = fyo.config.get(ConfigKeys.Telemetry);
return telemetrySetting !== TelemetrySetting.dontLogAnything;
}
function shouldNotStore(error: Error) {
return [MandatoryError, ValidationError].some(
return [MandatoryError, ValidationError, NotFoundError].some(
(errorClass) => error instanceof errorClass
);
}
@ -43,23 +40,14 @@ async function reportError(errorLogObj: ErrorLog, cb?: Function) {
cb?.();
}
function getToastProps(errorLogObj: ErrorLog, canLog: boolean, cb?: Function) {
function getToastProps(errorLogObj: ErrorLog, cb?: Function) {
const props: ToastOptions = {
message: errorLogObj.name ?? t`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;
}
@ -76,13 +64,12 @@ export function getErrorLogObject(
return errorLogObj;
}
export function handleError(
export async function handleError(
shouldLog: boolean,
error: Error,
more?: Record<string, unknown>,
cb?: Function
) {
fyo.telemetry.error(error.name);
if (shouldLog) {
console.error(error);
}
@ -93,17 +80,14 @@ export function handleError(
const errorLogObj = getErrorLogObject(error, more ?? {});
const canLog = getCanLog();
if (canLog) {
reportError(errorLogObj, cb);
} else {
showToast(getToastProps(errorLogObj, canLog, cb));
}
await reportError(errorLogObj, cb);
const toastProps = getToastProps(errorLogObj, cb);
await showToast(toastProps);
}
export async function handleErrorWithDialog(error: Error, doc?: Doc) {
const errorMessage = getErrorMessage(error, doc);
handleError(false, error, { errorMessage, doc });
await handleError(false, error, { errorMessage, doc });
const name = error.name ?? t`Error`;
await showMessageDialog({ message: name, detail: errorMessage });
@ -125,7 +109,7 @@ export function getErrorHandled(func: Function) {
try {
return await func(...args);
} catch (error) {
handleError(false, error as Error, {
await handleError(false, error as Error, {
functionName: func.name,
functionArgs: args,
});
@ -143,9 +127,9 @@ export function getErrorHandledSync(func: Function) {
handleError(false, error as Error, {
functionName: func.name,
functionArgs: args,
}).then(() => {
throw error;
});
throw error;
}
};
}

View File

@ -1,7 +1,7 @@
import { Fyo } from 'fyo';
import { getRegionalModels, models } from 'models';
import { ModelNameEnum } from 'models/types';
import { getValueMapFromList } from 'utils';
import { getRandomString, getValueMapFromList } from 'utils';
export const fyo = new Fyo({ isTest: false, isElectron: true });
@ -32,6 +32,7 @@ export async function initializeInstance(
await setSingles(fyo);
await setCreds(fyo);
await setVersion(fyo);
await setInstanceId(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) {
const currencies = (await fyo.db.getAll(ModelNameEnum.Currency, {
fields: ['name', 'symbol'],

View File

@ -119,21 +119,38 @@
<!-- Language Selector -->
<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%)"
>
<Button
class="text-sm w-40"
@click="createDemo"
:disabled="creatingDemo"
>{{ creatingDemo ? t`Please Wait` : t`Create Demo` }}</Button
>
<LanguageSelector
v-show="!creatingDemo"
class="w-40 bg-gray-100 rounded-md"
input-class="text-sm bg-transparent"
class="text-sm w-40 bg-gray-100 rounded-md"
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>
<Loading
@ -153,7 +170,6 @@ import { t } from 'fyo';
import { ConfigKeys } from 'fyo/core/types';
import { addNewFile } from 'fyo/telemetry/helpers';
import { DateTime } from 'luxon';
import Button from 'src/components/Button.vue';
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import FeatherIcon from 'src/components/FeatherIcon.vue';
import Loading from 'src/components/Loading.vue';
@ -217,7 +233,7 @@ export default {
this.creatingDemo = true;
const baseCount = fyo.store.isDevelopment ? 1000 : 150;
const companyName = await setupDummyInstance(
const { companyName, instanceId } = await setupDummyInstance(
filePath,
fyo,
1,
@ -230,9 +246,10 @@ export default {
addNewFile(
companyName,
fyo,
fyo.config.get(ConfigKeys.Files, []),
filePath
filePath,
instanceId,
fyo.config.get(ConfigKeys.Files),
fyo
);
fyo.purgeCache();
@ -292,7 +309,6 @@ export default {
components: {
LanguageSelector,
WindowControls,
Button,
Loading,
FeatherIcon,
},

View File

@ -8,26 +8,22 @@
:emit-change="true"
@change="forwardChangeEvent"
/>
<div class="flex flex-row justify-between items-center w-full">
<div class="flex items-center">
<FormControl
:df="df"
:value="telemetry"
@change="setValue"
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>
<div
class="flex flex-row justify-between items-center w-full text-gray-900"
>
<LanguageSelector
class="text-sm w-40 bg-gray-100 rounded-md"
input-class="py-1.5 bg-transparent"
/>
<button
class="
text-gray-900 text-sm
text-sm
bg-gray-100
hover:bg-gray-200
rounded-md
px-4
py-1.5
w-40
"
@click="checkForUpdates(true)"
>
@ -39,10 +35,7 @@
<script>
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 FormControl from 'src/components/Controls/FormControl.vue';
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import TwoColumnForm from 'src/components/TwoColumnForm';
import { fyo } from 'src/initFyo';
@ -52,7 +45,6 @@ import { getCountryInfo } from 'utils/misc';
export default {
name: 'TabSystem',
components: {
FormControl,
TwoColumnForm,
LanguageSelector,
},
@ -67,22 +59,9 @@ export default {
this.doc = fyo.singles.SystemSettings;
this.companyName = fyo.singles.AccountingSettings.companyName;
this.telemetry = fyo.config.get(ConfigKeys.Telemetry);
window.gci = getCountryInfo
window.gci = getCountryInfo;
},
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() {
return fyo.schemaMap.SystemSettings.quickEditFields.map((f) =>
fyo.getField(ModelNameEnum.SystemSettings, f)
@ -91,16 +70,6 @@ export default {
},
methods: {
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) {
this.$emit('change', ...args);
},

View File

@ -81,7 +81,6 @@ function setErrorHandlers(app: VueApp) {
const { fullPath, params } = vm.$route;
more.fullPath = fullPath;
more.params = stringifyCircular(params ?? {});
more.data = stringifyCircular(vm.$data ?? {}, true, true);
more.props = stringifyCircular(vm.$props ?? {}, true, true);
}

View File

@ -1,7 +1,6 @@
import { ipcRenderer } from 'electron';
import { handleError } from 'src/errorHandling';
import { fyo } from 'src/initFyo';
import { startTelemetry } from 'src/utils/misc';
import { showToast } from 'src/utils/ui';
import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages';
@ -57,7 +56,7 @@ export default function registerIpcRendererListeners() {
document.addEventListener('visibilitychange', function () {
const { visibilityState } = document;
if (visibilityState === 'visible' && !fyo.telemetry.started) {
startTelemetry();
fyo.telemetry.start();
}
if (visibilityState !== 'hidden') {

View File

@ -1,4 +1,3 @@
import { NounEnum, Verb } from 'fyo/telemetry/types';
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
import Dashboard from 'src/pages/Dashboard/Dashboard.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 Settings from 'src/pages/Settings/Settings.vue';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import { fyo } from './initFyo';
const routes: RouteRecordRaw[] = [
{
@ -120,26 +118,4 @@ const routes: RouteRecordRaw[] = [
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;

View File

@ -2,15 +2,16 @@ import { Fyo } from 'fyo';
import { ConfigFile, DocValueMap } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { createNumberSeries } from 'fyo/model/naming';
import { getId } from 'fyo/telemetry/helpers';
import {
DEFAULT_CURRENCY,
DEFAULT_LOCALE,
DEFAULT_SERIES_START,
} from 'fyo/utils/consts';
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
import { ModelNameEnum } from 'models/types';
import { initializeInstance } from 'src/initFyo';
import { createRegionalRecords } from 'src/regional';
import { getRandomString } from 'utils';
import { getCountryCodeFromCountry, getCountryInfo } from 'utils/misc';
import { CountryInfo } from 'utils/types';
import { CreateCOA } from './createCOA';
@ -93,10 +94,12 @@ async function updateSystemSettings(
const locale = countryOptions.locale ?? DEFAULT_LOCALE;
const countryCode = getCountryCodeFromCountry(country);
const systemSettings = await fyo.doc.getSingle('SystemSettings');
const instanceId = getRandomString();
systemSettings.setAndSync({
locale,
currency,
instanceId,
countryCode,
});
}
@ -156,18 +159,22 @@ async function createAccountRecords(
}
async function completeSetup(companyName: string, fyo: Fyo) {
updateInitializationConfig(companyName, fyo);
await updateInitializationConfig(companyName, fyo);
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 files = fyo.config.get('files', []) as ConfigFile[];
files.forEach((file) => {
if (file.dbPath === dbPath) {
file.companyName = companyName;
file.id = getId();
file.id = instanceId;
}
});

View File

@ -71,6 +71,8 @@ export async function incrementOpenCount(dbPath: string) {
ModelNameEnum.AccountingSettings,
'companyName'
)) as string;
let openCount = 0;
const files = fyo.config.get(ConfigKeys.Files) as ConfigFile[];
for (const file of files) {
if (file.companyName !== companyName || file.dbPath !== dbPath) {
@ -79,20 +81,10 @@ export async function incrementOpenCount(dbPath: string) {
file.openCount ??= 0;
file.openCount += 1;
openCount = file.openCount;
break;
}
fyo.config.set(ConfigKeys.Files, files);
}
export async function startTelemetry() {
fyo.telemetry.interestingDocs = [
ModelNameEnum.Payment,
ModelNameEnum.SalesInvoice,
ModelNameEnum.PurchaseInvoice,
ModelNameEnum.JournalEntry,
ModelNameEnum.Party,
ModelNameEnum.Item,
];
await fyo.telemetry.start();
return openCount;
}