Skip to content

Commit

Permalink
Add support for specifying an entity's shortName
Browse files Browse the repository at this point in the history
- Remove CatalogEntityItem as it is a needless abstraction

- Refix transparent background bug after bad rebase

- Improve type safety around CatalogEntities by having CatalogEntitySpec
  be Partial<Record<string, unknown>>

- Cleanup implementation of catalogSyncToRendererInjectable

- Always use EntityPreferencesStore as an injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Make CatalogEntityRegistry.init redundent
- Make all IPC injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Ensure that catalog is initialized during startFrame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Fix tests that overroad catalog sync
- Actually use the catalog sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Add behavioural tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Don't assign in a computed

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Make each store have a seperate migration version

- Allows each store to only bump its version when necessary
- Allows more decoupling from the extension API version so that PRs go stale slower

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Add behavioural tests for short name length

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Fix uses of testUsingTakeTime

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Fix test flakiness by removing animate delay in tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Update snapshots after removing timing sideeffects

Signed-off-by: Sebastian Malton <sebastian@malton.name>
  • Loading branch information
Nokel81 committed Jan 24, 2023
1 parent 0066ca4 commit e702b66
Show file tree
Hide file tree
Showing 198 changed files with 13,726 additions and 7,025 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@
"@testing-library/dom": "^7.31.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^13.5.0",
"@testing-library/user-event": "^14.4.3",
"@types/byline": "^4.2.33",
"@types/chart.js": "^2.9.36",
"@types/circular-dependency-plugin": "5.0.5",
Expand Down
4 changes: 2 additions & 2 deletions src/common/__tests__/cluster-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import directoryForTempInjectable from "../app-paths/directory-for-temp/director
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable";
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
import type { WriteJsonSync } from "../fs/write-json-sync.injectable";
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
import type { ReadFileSync } from "../fs/read-file-sync.injectable";
Expand All @@ -27,6 +26,7 @@ import type { WriteFileSync } from "../fs/write-file-sync.injectable";
import writeFileSyncInjectable from "../fs/write-file-sync.injectable";
import type { WriteBufferSync } from "../fs/write-buffer-sync.injectable";
import writeBufferSyncInjectable from "../fs/write-buffer-sync.injectable";
import clusterStoreMigrationVersionInjectable from "../cluster-store/migration-version.injectable";

// NOTE: this is intended to read the actual file system
const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png");
Expand Down Expand Up @@ -281,7 +281,7 @@ describe("cluster-store", () => {
});
writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon);

di.override(storeMigrationVersionInjectable, () => "3.6.0");
di.override(clusterStoreMigrationVersionInjectable, () => "3.6.0");

clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load();
Expand Down
7 changes: 4 additions & 3 deletions src/common/__tests__/hotbar-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-
import loggerInjectable from "../logger.injectable";
import type { Logger } from "../logger";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
import hotbarStoreMigrationVersionInjectable from "../hotbars/migration-version.injectable";

function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
return {
Expand Down Expand Up @@ -260,7 +260,7 @@ describe("HotbarStore", () => {
});
});

describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
describe("given data from 5.0.0-beta.3 and version being 5.6.0-alpha.7", () => {
beforeEach(() => {
const writeJsonSync = di.inject(writeJsonSyncInjectable);

Expand Down Expand Up @@ -322,7 +322,7 @@ describe("HotbarStore", () => {
],
});

di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
di.override(hotbarStoreMigrationVersionInjectable, () => "5.6.0-alpha.7");

hotbarStore = di.inject(hotbarStoreInjectable);

Expand All @@ -349,6 +349,7 @@ describe("HotbarStore", () => {
name: "my-aws-cluster",
source: "local",
uid: "some-aws-id",
shortName: "mac",
},
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/common/__tests__/user-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import type { ClusterStoreModel } from "../cluster-store/cluster-store";
import { defaultThemeId } from "../vars";
import writeFileInjectable from "../fs/write-file.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
import releaseChannelInjectable from "../vars/release-channel.injectable";
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
import writeFileSyncInjectable from "../fs/write-file-sync.injectable";
import userStoreMigrationVersionInjectable from "../user-store/migration-version.injectable";

describe("user store tests", () => {
let userStore: UserStore;
Expand Down Expand Up @@ -88,7 +88,7 @@ describe("user store tests", () => {

writeFileSync("/some/other/path", "is file");

di.override(storeMigrationVersionInjectable, () => "10.0.0");
di.override(userStoreMigrationVersionInjectable, () => "10.0.0");

userStore.load();
});
Expand Down
32 changes: 21 additions & 11 deletions src/common/base-store/base-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,27 @@ import type { Migrations, Options as ConfOptions } from "conf/dist/source/types"
import type { IEqualsComparer } from "mobx";
import { makeObservable, reaction } from "mobx";
import { disposer, isPromiseLike, toJS } from "../utils";
import { broadcastMessage } from "../ipc";
import isEqual from "lodash/isEqual";
import { kebabCase } from "lodash";
import type { GetConfigurationFileModel } from "../get-configuration-file-model/get-configuration-file-model.injectable";
import type { Logger } from "../logger";
import type { PersistStateToConfig } from "./save-to-file";
import type { GetBasenameOfPath } from "../path/get-basename.injectable";
import type { EnlistMessageChannelListener } from "../utils/channel/enlist-message-channel-listener-injection-token";
import type { SendMessageToChannel } from "../utils/channel/message-to-channel-injection-token";
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";

export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations" | "projectVersion"> {
syncOptions?: {
fireImmediately?: boolean;
equals?: IEqualsComparer<T>;
};
configName: string;
readonly configName: string;
}

export interface IpcChannelPrefixes {
local: string;
remote: string;
readonly local: string;
readonly remote: string;
}

export interface BaseStoreDependencies {
Expand All @@ -41,6 +42,7 @@ export interface BaseStoreDependencies {
persistStateToConfig: PersistStateToConfig;
getBasenameOfPath: GetBasenameOfPath;
enlistMessageChannelListener: EnlistMessageChannelListener;
sendMessageToChannel: SendMessageToChannel;
}

/**
Expand Down Expand Up @@ -90,20 +92,26 @@ export abstract class BaseStore<T extends object> {
const name = this.dependencies.getBasenameOfPath(config.path);

const disableSync = () => this.syncDisposers();

const sendChannel: MessageChannel<T> = {
id: `${this.dependencies.ipcChannelPrefixes.remote}:${config.path}`,
};
const receiveChannel: MessageChannel<T> = {
id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`,
};

const enableSync = () => {
this.syncDisposers.push(
reaction(
() => toJS(this.toJSON()), // unwrap possible observables and react to everything
model => {
this.dependencies.persistStateToConfig(config, model);
broadcastMessage(`${this.dependencies.ipcChannelPrefixes.remote}:${config.path}`, model);
this.dependencies.sendMessageToChannel(sendChannel, model);
},
this.params.syncOptions,
),
this.dependencies.enlistMessageChannelListener({
channel: {
id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`,
},
channel: receiveChannel,
handler: (model) => {
this.dependencies.logger.silly(`[${this.displayName}]: syncing ${name}`, { model });

Expand All @@ -113,11 +121,13 @@ export abstract class BaseStore<T extends object> {

// todo: use "resourceVersion" if merge required (to avoid equality checks => better performance)
if (!isEqual(this.toJSON(), model)) {
this.fromStore(model as T);
this.fromStore(model);
}

if (this.dependencies.shouldDisableSyncInListener) {
enableSync();
setImmediate(() => {
enableSync();
});
}
},
}),
Expand Down
2 changes: 1 addition & 1 deletion src/common/catalog-entities/kubernetes-cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class KubernetesCluster<
}
}

async onRun(context: CatalogEntityActionContext) {
onRun(context: CatalogEntityActionContext) {
context.navigate(`/cluster/${this.getId()}`);
}

Expand Down
10 changes: 5 additions & 5 deletions src/common/catalog-entities/web-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog";
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
import productNameInjectable from "../vars/product-name.injectable";
import weblinkStoreInjectable from "../weblinks-store/weblink-store.injectable";
Expand All @@ -15,18 +15,18 @@ export interface WebLinkStatus extends CatalogEntityStatus {
phase: WebLinkStatusPhase;
}

export interface WebLinkSpec {
export interface WebLinkSpec extends CatalogEntitySpec {
url: string;
}

export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus, WebLinkSpec> {
public static readonly apiVersion = "entity.k8slens.dev/v1alpha1";
public static readonly kind = "WebLink";

public readonly apiVersion = WebLink.apiVersion;
public readonly kind = WebLink.kind;
public readonly apiVersion: string = WebLink.apiVersion;
public readonly kind: string = WebLink.kind;

async onRun() {
onRun() {
window.open(this.spec.url, "_blank");
}

Expand Down
32 changes: 32 additions & 0 deletions src/common/catalog/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/

import { computeDefaultShortName } from "../helpers";

describe("catalog helper tests", () => {
describe("computeDefaultShortName", () => {
it.each([
["a", "a"],
["", "??"],
[1, "??"],
[true, "??"],
["ab", "ab"],
["abc", "ab"],
["abcde", "ab"],
["ab-cd", "ac"],
["ab-cd la", "al"],
["ab-cd la_1", "al"],
["ab-cd la 1_3", "al1"],
["ab-cd la 1_3 lk", "al1"],
["ab-cd la 1_3 lk aj", "al1"],
["😀 a", "😀a"],
["😀😎 a", "😀a"],
["🇫🇮 Finland", "🇫🇮F"],
["إعجم", "إع"],
])("should compute from %p into %p", (input: any, output: string) => {
expect(computeDefaultShortName(input)).toBe(output);
});
});
});
6 changes: 3 additions & 3 deletions src/common/catalog/catalog-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export interface CatalogEntityAddMenuContext {
menuItems: CatalogEntityAddMenu[];
}

export type CatalogEntitySpec = Record<string, any>;
export type CatalogEntitySpec = Partial<Record<string, unknown>>;


export interface CatalogEntityData<
Expand Down Expand Up @@ -359,8 +359,6 @@ export abstract class CatalogEntity<
@observable spec: Spec;

constructor({ metadata, status, spec }: CatalogEntityData<Metadata, Status, Spec>) {
makeObservable(this);

if (!metadata || typeof metadata !== "object") {
throw new TypeError("CatalogEntity's metadata must be a defined object");
}
Expand All @@ -376,6 +374,8 @@ export abstract class CatalogEntity<
this.metadata = metadata;
this.status = status;
this.spec = spec;

makeObservable(this);
}

/**
Expand Down
83 changes: 83 additions & 0 deletions src/common/catalog/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/

import type { CatalogEntity } from "./catalog-entity";
import GraphemeSplitter from "grapheme-splitter";
import { hasOwnProperty, hasTypedProperty, isObject, isString, iter } from "../utils";

function getNameParts(name: string): string[] {
const byWhitespace = name.split(/\s+/);

if (byWhitespace.length > 1) {
return byWhitespace;
}

const byDashes = name.split(/[-_]+/);

if (byDashes.length > 1) {
return byDashes;
}

return name.split(/@+/);
}

export function limitGraphemeLengthOf(src: string, count: number): string {
const splitter = new GraphemeSplitter();

return iter
.chain(splitter.iterateGraphemes(src))
.take(count)
.join("");
}

export function computeDefaultShortName(name: string) {
if (!name || typeof name !== "string") {
return "??";
}

const [rawFirst, rawSecond, rawThird] = getNameParts(name);
const splitter = new GraphemeSplitter();
const first = splitter.iterateGraphemes(rawFirst);
const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first;
const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty<string>();

return iter.chain(iter.take(first, 1))
.concat(iter.take(second, 1))
.concat(iter.take(third, 1))
.join("");
}

export function getShortName(entity: CatalogEntity): string {
return entity.metadata.shortName || computeDefaultShortName(entity.getName());
}

export function getIconColourHash(entity: CatalogEntity): string {
return `${entity.metadata.name}-${entity.metadata.source}`;
}

export function getIconBackground(entity: CatalogEntity): string | undefined {
if (isObject(entity.spec.icon)) {
if (hasTypedProperty(entity.spec.icon, "background", isString)) {
return entity.spec.icon.background;
}

return hasOwnProperty(entity.spec.icon, "src")
? "transparent"
: undefined;
}

return undefined;
}

export function getIconMaterial(entity: CatalogEntity): string | undefined {
if (
isObject(entity.spec.icon)
&& hasTypedProperty(entity.spec.icon, "material", isString)
) {
return entity.spec.icon.material;
}

return undefined;
}
6 changes: 4 additions & 2 deletions src/common/cluster-store/cluster-store.injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import loggerInjectable from "../logger.injectable";
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
import storeMigrationsInjectable from "../base-store/migrations.injectable";
import { clusterStoreMigrationInjectionToken } from "./migration-token";
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
import clusterStoreMigrationVersionInjectable from "./migration-version.injectable";
import { sendMessageToChannelInjectionToken } from "../utils/channel/message-to-channel-injection-token";

const clusterStoreInjectable = getInjectable({
id: "cluster-store",
Expand All @@ -29,13 +30,14 @@ const clusterStoreInjectable = getInjectable({
directoryForUserData: di.inject(directoryForUserDataInjectable),
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
logger: di.inject(loggerInjectable),
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
storeMigrationVersion: di.inject(clusterStoreMigrationVersionInjectable),
migrations: di.inject(storeMigrationsInjectable, clusterStoreMigrationInjectionToken),
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
sendMessageToChannel: di.inject(sendMessageToChannelInjectionToken),
}),
});

Expand Down
Loading

0 comments on commit e702b66

Please sign in to comment.