Skip to content

Commit

Permalink
Add ModelFeatureFlag and improve plugin support
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtechszocs committed May 15, 2019
1 parent 89dfb5c commit e712229
Show file tree
Hide file tree
Showing 15 changed files with 344 additions and 145 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"@typescript-eslint/parser": "^1.7.0",
"bootstrap-sass": "^3.3.7",
"cache-loader": "1.x",
"chalk": "2.3.x",
"chromedriver": "^2.43.3",
"circular-dependency-plugin": "5.0.2",
"css-loader": "0.28.x",
Expand Down
13 changes: 12 additions & 1 deletion frontend/packages/console-demo-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ResourceNSNavItem,
ResourceListPage,
ResourceDetailPage,
ModelFeatureFlag,
} from '@console/plugin-sdk';

// TODO(vojtech): internal code needed by plugins should be moved to console-shared package
Expand All @@ -13,7 +14,8 @@ type ConsumedExtensions =
| HrefNavItem
| ResourceNSNavItem
| ResourceListPage
| ResourceDetailPage;
| ResourceDetailPage
| ModelFeatureFlag;

const plugin: Plugin<ConsumedExtensions> = [
{
Expand All @@ -23,6 +25,7 @@ const plugin: Plugin<ConsumedExtensions> = [
componentProps: {
name: 'Test Href Link',
href: '/test',
required: 'TEST_MODEL_FLAG',
},
},
},
Expand All @@ -33,6 +36,7 @@ const plugin: Plugin<ConsumedExtensions> = [
componentProps: {
name: 'Test ResourceNS Link',
resource: 'pods',
required: 'TEST_MODEL_FLAG',
},
},
},
Expand All @@ -50,6 +54,13 @@ const plugin: Plugin<ConsumedExtensions> = [
loader: () => import('@console/internal/components/pod' /* webpackChunkName: "pod" */).then(m => m.PodsDetailsPage),
},
},
{
type: 'FeatureFlag/Model',
properties: {
model: PodModel,
flag: 'TEST_MODEL_FLAG',
},
},
];

export default plugin;
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
Package,
PluginPackage,
isValidPluginPackage,
resolveActivePlugins,
getActivePluginsModule,
} from '..';

const templatePackage: Package = { name: 'test', version: '1.2.3', readme: '', _id: '@' };

describe('codegen', () => {

describe('isValidPluginPackage', () => {
it('returns false if package.consolePlugin is missing', () => {
expect(isValidPluginPackage({
...templatePackage,
})).toBe(false);
});

it('returns false if package.consolePlugin.entry is missing', () => {
expect(isValidPluginPackage({
...templatePackage,
consolePlugin: {},
})).toBe(false);
});

it('returns false if package.consolePlugin.entry is an empty string', () => {
expect(isValidPluginPackage({
...templatePackage,
consolePlugin: { entry: '' },
})).toBe(false);
});

it('returns true if package.consolePlugin.entry is not an empty string', () => {
expect(isValidPluginPackage({
...templatePackage,
consolePlugin: { entry: 'plugin.ts' },
})).toBe(true);
});
});

describe('resolveActivePlugins', () => {
it('filters out packages which are not listed in appPackage.dependencies', () => {
const appPackage: Package = {
...templatePackage,
name: 'app',
dependencies: {
'foo': '0.1.2',
'bar': '1.2.3',
},
};

const pluginPackages: PluginPackage[] = [
{
...templatePackage,
name: 'bar',
version: '1.2.3',
consolePlugin: { entry: 'plugin.ts' },
},
{
...templatePackage,
name: 'qux',
version: '2.3.4',
consolePlugin: { entry: 'plugin.ts' },
},
];

expect(resolveActivePlugins(appPackage, pluginPackages)).toEqual([
{ ...pluginPackages[0] },
]);
});
});

describe('getActivePluginsModule', () => {
it('returns the source of a module that exports the list of active plugins', () => {
const pluginPackages: PluginPackage[] = [
{
...templatePackage,
name: 'bar',
version: '1.2.3',
consolePlugin: { entry: 'src/plugin.ts' },
},
{
...templatePackage,
name: 'qux-plugin',
version: '2.3.4',
consolePlugin: { entry: 'index.ts' },
},
];

const expectedModule = `
const activePlugins = [];
import plugin_0 from 'bar/src/plugin.ts';
activePlugins.push(plugin_0);
import plugin_1 from 'qux-plugin/index.ts';
activePlugins.push(plugin_1);
export default activePlugins;
`.replace(/^\s+/gm, '');

expect(getActivePluginsModule(pluginPackages)).toBe(expectedModule);
});
});

});
55 changes: 31 additions & 24 deletions frontend/packages/console-plugin-sdk/src/codegen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@
import * as path from 'path';
import * as readPkg from 'read-pkg';

type Package = readPkg.NormalizedPackageJson;
export type Package = readPkg.NormalizedPackageJson;

interface PluginPackage extends Package {
export interface PluginPackage extends Package {
consolePlugin: {
entry: string;
}
}

function isValidPluginPackage(pkg: Package): pkg is PluginPackage {
return (pkg as PluginPackage).consolePlugin && typeof (pkg as PluginPackage).consolePlugin.entry === 'string';
export function isValidPluginPackage(pkg: Package): pkg is PluginPackage {
if (!(pkg as PluginPackage).consolePlugin) {
return false;
}

const entry = (pkg as PluginPackage).consolePlugin.entry;
return typeof entry === 'string' && entry.length > 0;
}

function readPackages(packageFiles: string[]) {
/**
* Read package metadata and detect any plugins.
*
* @param packageFiles Paths to `package.json` files (all the monorepo packages).
*/
export function readPackages(packageFiles: string[]) {
const pkgList: Package[] = packageFiles.map(file => readPkg.sync({ cwd: path.dirname(file), normalize: true }));

return {
Expand All @@ -24,32 +34,29 @@ function readPackages(packageFiles: string[]) {
};
}

/**
* Resolve the list of active plugins.
*/
export function resolveActivePlugins(appPackage: Package, pluginPackages: PluginPackage[]) {
return pluginPackages.filter(pkg => appPackage.dependencies[pkg.name] === pkg.version);
}

/**
* Generate the "active plugins" module source.
*
* @param packageFiles Paths to `package.json` files (all the monorepo packages).
*/
export function getActivePluginsModule(packageFiles: string[]): string {
const { appPackage, pluginPackages } = readPackages(packageFiles);
export function getActivePluginsModule(activePluginPackages: PluginPackage[]): string {
let output = `
const activePlugins = [];
`;

if (appPackage) {
for (const depName of Object.keys(appPackage.dependencies)) {
const depVersion = appPackage.dependencies[depName];
const foundPluginPackage = pluginPackages.find(pkg => pkg.name === depName && pkg.version === depVersion);

if (foundPluginPackage) {
const importName = `plugin_${pluginPackages.indexOf(foundPluginPackage)}`;
const importPath = `${foundPluginPackage.name}/${foundPluginPackage.consolePlugin.entry}`;
output = `
${output}
import ${importName} from '${importPath}';
activePlugins.push(${importName});
`;
}
}
for (const pkg of activePluginPackages) {
const importName = `plugin_${activePluginPackages.indexOf(pkg)}`;
const importPath = `${pkg.name}/${pkg.consolePlugin.entry}`;
output = `
${output}
import ${importName} from '${importPath}';
activePlugins.push(${importName});
`;
}

output = `
Expand Down
6 changes: 5 additions & 1 deletion frontend/packages/console-plugin-sdk/src/registry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as _ from 'lodash-es';
import { Extension, PluginList, isNavItem, isResourcePage } from './typings';
import { Extension, PluginList, isNavItem, isResourcePage, isFeatureFlag } from './typings';

/**
* Registry used to query for Console extensions.
Expand All @@ -20,4 +20,8 @@ export class ExtensionRegistry {
return this.extensions.filter(isResourcePage);
}

public getFeatureFlags() {
return this.extensions.filter(isFeatureFlag);
}

}
24 changes: 24 additions & 0 deletions frontend/packages/console-plugin-sdk/src/typings/features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Extension } from '.';
import { K8sKind } from '@console/internal/module/k8s';

namespace ExtensionProperties {
export interface ModelFeatureFlag {
model: K8sKind;
flag: string;
}
}

export interface ModelFeatureFlag extends Extension<ExtensionProperties.ModelFeatureFlag> {
type: 'FeatureFlag/Model';
}

// TODO(vojtech): add ActionFeatureFlag
export type FeatureFlag = ModelFeatureFlag;

export function isModelFeatureFlag(e: Extension<any>): e is ModelFeatureFlag {
return e.type === 'FeatureFlag/Model';
}

export function isFeatureFlag(e: Extension<any>): e is FeatureFlag {
return isModelFeatureFlag(e);
}
1 change: 1 addition & 0 deletions frontend/packages/console-plugin-sdk/src/typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ export type PluginList = Plugin<Extension<any>>[];

// TODO(vojtech): internal code needed by plugin SDK should be moved to console-shared package

export * from './features';
export * from './nav';
export * from './pages';
42 changes: 22 additions & 20 deletions frontend/packages/console-plugin-sdk/src/typings/nav.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
import { Extension } from '.';
import { K8sKind } from '@console/internal/module/k8s';

export interface NavItemProperties {
// TODO(vojtech): link to existing nav sections by value
section: 'Home' | 'Workloads';
componentProps: {
name: string;
required?: string;
disallowed?: string;
startsWith?: string[];
namespace ExtensionProperties {
interface NavItem {
// TODO(vojtech): link to existing nav sections by value
section: 'Home' | 'Workloads';
componentProps: {
name: string;
required?: string;
disallowed?: string;
startsWith?: string[];
}
}
}

export interface HrefProperties extends NavItemProperties {
componentProps: NavItemProperties['componentProps'] & {
href: string;
activePath?: string;
export interface HrefNavItem extends NavItem {
componentProps: NavItem['componentProps'] & {
href: string;
activePath?: string;
}
}
}

export interface ResourceNSProperties extends NavItemProperties {
componentProps: NavItemProperties['componentProps'] & {
resource: string;
model?: K8sKind;
export interface ResourceNSNavItem extends NavItem {
componentProps: NavItem['componentProps'] & {
resource: string;
model?: K8sKind;
}
}
}

export interface HrefNavItem extends Extension<HrefProperties> {
export interface HrefNavItem extends Extension<ExtensionProperties.HrefNavItem> {
type: 'NavItem/Href';
}

export interface ResourceNSNavItem extends Extension<ResourceNSProperties> {
export interface ResourceNSNavItem extends Extension<ExtensionProperties.ResourceNSNavItem> {
type: 'NavItem/ResourceNS';
}

Expand Down
12 changes: 7 additions & 5 deletions frontend/packages/console-plugin-sdk/src/typings/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import * as React from 'react';
import { Extension } from '.';
import { K8sKind } from '@console/internal/module/k8s';

export interface ResourcePageProperties {
model: K8sKind;
loader: () => Promise<React.ComponentType<any>>;
namespace ExtensionProperties {
export interface ResourcePage {
model: K8sKind;
loader: () => Promise<React.ComponentType<any>>;
}
}

export interface ResourceListPage extends Extension<ResourcePageProperties> {
export interface ResourceListPage extends Extension<ExtensionProperties.ResourcePage> {
type: 'ResourcePage/List';
}

export interface ResourceDetailPage extends Extension<ResourcePageProperties> {
export interface ResourceDetailPage extends Extension<ExtensionProperties.ResourcePage> {
type: 'ResourcePage/Detail';
}

Expand Down
Loading

0 comments on commit e712229

Please sign in to comment.