Initial telemetry extension (#1067)

* initial telemetry extension

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* Fix preferences saving and default value

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Jari KolehmainenandLauri Nevala authored and GitHub committed 2020-10-13 12:57:38 +03:00
1 parent 4f98a01a8b
commit 45abc740e9
28 files changed
+3837 -48

No files matched your search

+4
View File
@@ -7,3 +7,7 @@ export function getAppVersion(): string {
export function getBundledKubectlVersion(): string {
return packageInfo.config.bundledKubectlVersion;
}
export function getBundledExtensions(): string[] {
return packageInfo.lens?.extensions || []
}
+27
View File
@@ -0,0 +1,27 @@
import { observable } from "mobx"
import React from "react"
export interface AppPreferenceComponents {
Hint: React.ComponentType<any>;
Input: React.ComponentType<any>;
}
export interface AppPreferenceRegistration {
title: string;
components: AppPreferenceComponents;
}
export class AppPreferenceRegistry {
preferences = observable.array<AppPreferenceRegistration>([], { deep: false });
add(preference: AppPreferenceRegistration) {
this.preferences.push(preference)
return () => {
this.preferences.replace(
this.preferences.filter(c => c !== preference)
)
};
}
}
export const appPreferenceRegistry = new AppPreferenceRegistry()
+1 -1
View File
@@ -1,7 +1,7 @@
import React from "react";
import { cssNames } from "../renderer/utils";
import { TabLayout } from "../renderer/components/layout/tab-layout";
import { PageRegistration } from "./page-store"
import { PageRegistration } from "./page-registry"
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
render() {
+1
View File
@@ -3,3 +3,4 @@ export type { LensExtensionRuntimeEnv } from "./lens-runtime";
// APIs
export * from "./lens-main-extension"
export { BaseStore } from "../common/base-store"
+6 -4
View File
@@ -6,7 +6,8 @@ import path from "path"
import { observable, reaction, toJS, } from "mobx"
import logger from "../main/logger"
import { app, remote, ipcRenderer } from "electron"
import { pageStore } from "./page-store";
import { pageRegistry } from "./page-registry";
import { appPreferenceRegistry } from "./app-preference-registry"
export interface InstalledExtension extends ExtensionModel {
manifestPath: string;
@@ -37,14 +38,15 @@ export class ExtensionLoader {
loadOnClusterRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer')
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
instance.registerPages(pageStore)
instance.registerPages(pageRegistry)
})
}
loadOnMainRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
logger.info('[EXTENSIONS-LOADER]: load on main renderer')
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
instance.registerPages(pageStore)
instance.registerPages(pageRegistry)
instance.registerAppPreferences(appPreferenceRegistry)
})
}
@@ -82,7 +84,7 @@ export class ExtensionLoader {
try {
if (ipcRenderer && extension.manifest.renderer) {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
} else if (extension.manifest.main) {
} else if (!ipcRenderer && extension.manifest.main) {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
}
if (extEntrypoint !== "") {
+5
View File
@@ -4,6 +4,7 @@ import fs from "fs-extra"
import logger from "../main/logger"
import { extensionPackagesRoot, InstalledExtension } from "./extension-loader"
import * as child_process from 'child_process';
import { getBundledExtensions } from "../common/utils/app-version"
type Dependencies = {
[name: string]: string;
@@ -80,7 +81,11 @@ export class ExtensionManager {
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
const paths = await fs.readdir(folderPath);
const extensions: InstalledExtension[] = []
const bundledExtensions = getBundledExtensions()
for (const fileName of paths) {
if (!bundledExtensions.includes(fileName)) {
continue
}
const absPath = path.resolve(folderPath, fileName);
const manifestPath = path.resolve(absPath, "package.json");
await fs.access(manifestPath, fs.constants.F_OK)
+3 -1
View File
@@ -4,10 +4,12 @@ export type { LensExtensionRuntimeEnv } from "./lens-renderer-runtime"
// APIs
export * from "./lens-extension"
export * from "./lens-renderer-extension"
export { DynamicPageType, PageStore } from "./page-store"
export { DynamicPageType, PageRegistry } from "./page-registry"
export { AppPreferenceRegistry } from "./app-preference-registry"
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps)
export * from "../renderer/components/icon"
export * from "../renderer/components/checkbox"
export * from "../renderer/components/tooltip"
export * from "../renderer/components/button"
export * from "../renderer/components/tabs"
+2 -2
View File
@@ -59,7 +59,7 @@ export class LensExtension implements ExtensionModel {
async enable(runtime: LensExtensionRuntimeEnv) {
this.isEnabled = true;
this.runtime = runtime;
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta());
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
this.onActivate();
}
@@ -69,7 +69,7 @@ export class LensExtension implements ExtensionModel {
this.runtime = null;
this.disposers.forEach(cleanUp => cleanUp());
this.disposers.length = 0;
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta());
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
}
// todo: add more hooks
+5 -7
View File
@@ -1,16 +1,14 @@
import type { PageStore } from "./extension-renderer-api"
import type { PageRegistration } from "./page-store"
import { LensExtension } from "./lens-extension"
import type { PageRegistry } from "./extension-renderer-api"
import { AppPreferenceRegistry } from "./app-preference-registry";
export class LensRendererExtension extends LensExtension {
registerPages(pageStore: PageStore) {
registerPages(pageStore: PageRegistry) {
return
}
// Runtime helpers
protected registerPage(pageStore: PageStore, params: PageRegistration) {
const dispose = pageStore.register(params);
this.disposers.push(dispose)
registerAppPreferences(appPreferenceStore: AppPreferenceRegistry) {
return
}
}
@@ -27,7 +27,7 @@ export interface PageComponents {
MenuIcon: React.ComponentType<IconProps>;
}
export class PageStore {
export class PageRegistry {
protected pages = observable.array<PageRegistration>([], { deep: false });
@computed get globalPages() {
@@ -39,7 +39,7 @@ export class PageStore {
}
// todo: verify paths to avoid collision with existing pages
register(params: PageRegistration) {
add(params: PageRegistration) {
this.pages.push(params);
return () => {
this.pages.replace(
@@ -49,4 +49,4 @@ export class PageStore {
}
}
export const pageStore = new PageStore();
export const pageRegistry = new PageRegistry();
+3 -1
View File
@@ -1,6 +1,7 @@
import "./components/app.scss"
import React from "react";
import * as Mobx from "mobx"
import * as LensMainExtensions from "../extensions/extension-api"
import * as LensExtensions from "../extensions/extension-renderer-api"
import { render, unmountComponentAtNode } from "react-dom";
import { isMac } from "../common/vars";
@@ -19,7 +20,8 @@ type AppComponent = React.ComponentType & {
export {
React,
Mobx,
LensExtensions
LensExtensions,
LensMainExtensions
}
export async function bootstrap(App: AppComponent) {
@@ -17,6 +17,7 @@ import { themeStore } from "../../theme.store";
import { history } from "../../navigation";
import { Tooltip } from "../tooltip";
import { KubectlBinaries } from "./kubectl-binaries";
import { appPreferenceRegistry } from "../../../extensions/app-preference-registry";
@observer
export class Preferences extends React.Component {
@@ -114,6 +115,7 @@ export class Preferences extends React.Component {
render() {
const { preferences } = userStore;
const extensionPreferences = appPreferenceRegistry.preferences
const header = (
<>
<h2>Preferences</h2>
@@ -185,15 +187,17 @@ export class Preferences extends React.Component {
<Trans>Does not affect cluster communications!</Trans>
</small>
<h2><Trans>Telemetry & Usage Tracking</Trans></h2>
<Checkbox
label={<Trans>Allow telemetry & usage tracking</Trans>}
value={preferences.allowTelemetry}
onChange={v => preferences.allowTelemetry = v}
/>
<small className="hint">
<Trans>Telemetry & usage data is collected to continuously improve the Lens experience.</Trans>
</small>
{extensionPreferences.map(({title, components: { Hint, Input}}) => {
return (
<div key={title}>
<h2>{title}</h2>
<Input />
<small className="hint">
<Hint />
</small>
</div>
)
})}
</WizardLayout>
</div>
);
+2 -2
View File
@@ -36,7 +36,7 @@ import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store
import logger from "../../main/logger";
import { clusterIpc } from "../../common/cluster-ipc";
import { webFrame } from "electron";
import { pageStore } from "../../extensions/page-store";
import { pageRegistry } from "../../extensions/page-registry";
import { DynamicPage } from "../../extensions/dynamic-page";
import { extensionLoader } from "../../extensions/extension-loader";
import { getLensRuntime } from "../../extensions/lens-runtime";
@@ -78,7 +78,7 @@ export class App extends React.Component {
<Route component={CustomResources} {...crdRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/>
{pageStore.clusterPages.map(page => {
{pageRegistry.clusterPages.map(page => {
return <Route {...page} key={page.path} render={() => <DynamicPage page={page}/>}/>
})}
<Redirect exact from="/" to={this.startURL}/>
@@ -14,7 +14,7 @@ import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings";
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route";
import { clusterStore } from "../../../common/cluster-store";
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
import { pageStore } from "../../../extensions/page-store";
import { pageRegistry } from "../../../extensions/page-registry";
@observer
export class ClusterManager extends React.Component {
@@ -63,7 +63,7 @@ export class ClusterManager extends React.Component {
<Route component={AddCluster} {...addClusterRoute} />
<Route component={ClusterView} {...clusterViewRoute} />
<Route component={ClusterSettings} {...clusterSettingsRoute} />
{pageStore.globalPages.map(({ path, components: { Page } }) => {
{pageRegistry.globalPages.map(({ path, components: { Page } }) => {
return <Route key={path} path={path} component={Page}/>
})}
<Redirect exact to={this.startUrl} />
@@ -22,7 +22,7 @@ import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route";
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
import { pageStore } from "../../../extensions/page-store";
import { pageRegistry } from "../../../extensions/page-registry";
interface Props {
className?: IClassName;
@@ -156,7 +156,7 @@ export class ClustersMenu extends React.Component<Props> {
)}
</div>
<div className="dynamic-pages">
{pageStore.globalPages.map(({ path, components: { MenuIcon } }) => {
{pageRegistry.globalPages.map(({ path, components: { MenuIcon } }) => {
return <MenuIcon key={path} onClick={() => navigate(path)}/>
})}
</div>
+2 -2
View File
@@ -28,7 +28,7 @@ import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resourc
import { CustomResources } from "../+custom-resources/custom-resources";
import { navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac"
import { pageStore } from "../../../extensions/page-store";
import { pageRegistry } from "../../../extensions/page-registry";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = {
@@ -184,7 +184,7 @@ export class Sidebar extends React.Component<Props> {
>
{this.renderCustomResources()}
</SidebarNavItem>
{pageStore.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
{pageRegistry.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
return (
<SidebarNavItem
key={path}