-
Notifications
You must be signed in to change notification settings - Fork 670
Add support for static plugins #1499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| import '../../../public/components/app'; | ||
| import '@console/internal/components/app'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "name": "@console/demo-plugin", | ||
| "version": "0.0.0-fixed", | ||
| "description": "Demo plugin for Console web application", | ||
| "private": true, | ||
| "dependencies": { | ||
| "@console/plugin-sdk": "0.0.0-fixed", | ||
| "@console/shared": "0.0.0-fixed" | ||
| }, | ||
| "consolePlugin": { | ||
| "entry": "src/plugin.ts" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { | ||
| Plugin, | ||
| HrefNavItem, | ||
| ResourceNSNavItem, | ||
| ResourceListPage, | ||
| ResourceDetailPage, | ||
| } from '@console/plugin-sdk'; | ||
|
|
||
| // TODO(vojtech): internal code needed by plugins should be moved to console-shared package | ||
| import { PodModel } from '@console/internal/models'; | ||
|
|
||
| type ConsumedExtensions = | ||
| | HrefNavItem | ||
| | ResourceNSNavItem | ||
| | ResourceListPage | ||
| | ResourceDetailPage; | ||
|
|
||
| const plugin: Plugin<ConsumedExtensions> = [ | ||
| { | ||
| type: 'NavItem/Href', | ||
| properties: { | ||
| section: 'Home', | ||
| componentProps: { | ||
| name: 'Test Href Link', | ||
| href: '/test', | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| type: 'NavItem/ResourceNS', | ||
| properties: { | ||
| section: 'Workloads', | ||
| componentProps: { | ||
| name: 'Test ResourceNS Link', | ||
| resource: 'pods', | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| type: 'ResourcePage/List', | ||
| properties: { | ||
| model: PodModel, | ||
| loader: () => import('@console/internal/components/pod' /* webpackChunkName: "pod" */).then(m => m.PodsPage), | ||
| }, | ||
| }, | ||
| { | ||
| type: 'ResourcePage/Detail', | ||
| properties: { | ||
| model: PodModel, | ||
| loader: () => import('@console/internal/components/pod' /* webpackChunkName: "pod" */).then(m => m.PodsDetailsPage), | ||
| }, | ||
| }, | ||
| ]; | ||
|
|
||
| export default plugin; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "name": "@console/plugin-sdk", | ||
| "version": "0.0.0-fixed", | ||
| "description": "Console static plugin SDK", | ||
| "private": true, | ||
| "main": "src/index.ts", | ||
| "scripts": { | ||
| "test": "yarn --cwd ../.. run test packages/console-plugin-sdk" | ||
|
||
| }, | ||
| "dependencies": { | ||
| "@console/shared": "0.0.0-fixed" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||
| /* eslint-env node */ | ||||||
|
|
||||||
| import * as path from 'path'; | ||||||
| import * as readPkg from 'read-pkg'; | ||||||
|
|
||||||
| type Package = readPkg.NormalizedPackageJson; | ||||||
|
|
||||||
| interface PluginPackage extends Package { | ||||||
| consolePlugin: { | ||||||
| entry: string; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function isValidPluginPackage(pkg: Package): pkg is PluginPackage { | ||||||
|
||||||
| function isValidPluginPackage(pkg: Package): pkg is PluginPackage { | |
| const isValidPluginPackage = (pkg: Package): pkg is PluginPackage => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll keep that in mind for my future changes.
Personally, I find the typical function declaration more readable:
function name(args): returnType { body }
vs.
const name = (args): returnType => { body }
anyway, I'll try to stick with existing conventions used in Console code.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export * from './typings'; | ||
| export * from './registry'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import * as _ from 'lodash-es'; | ||
| import { Extension, PluginList, isNavItem, isResourcePage } from './typings'; | ||
|
|
||
| /** | ||
| * Registry used to query for Console extensions. | ||
| */ | ||
| export class ExtensionRegistry { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking forward to seeing this removed in favor of a HOC. |
||
|
|
||
| private readonly extensions: Extension<any>[]; | ||
|
|
||
| public constructor(plugins: PluginList) { | ||
| this.extensions = _.flatMap(plugins); | ||
| } | ||
|
|
||
| public getNavItems(section: string) { | ||
| return this.extensions.filter(isNavItem).filter(e => e.properties.section === section); | ||
| } | ||
|
|
||
| public getResourcePages() { | ||
| return this.extensions.filter(isResourcePage); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| /** | ||
| * An extension of the Console web application. | ||
| * | ||
| * Each extension is a realization (instance) of an extension `type` using the | ||
| * parameters provided via the `properties` object. | ||
| * | ||
| * Core extension types should follow `Category` or `Category/Specialization` | ||
| * format, e.g. `NavItem/Href`. | ||
| * | ||
| * @todo(vojtech) write ESLint rule to guard against extension type duplicity | ||
| */ | ||
| export interface Extension<P> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In TypeScript, type aliases provide a name to refer to any kind of type, while interfaces are used to name object specific types.
When describing object types, I'd stick to the TS spec recommendation and prefer using interfaces over type aliases. Using interfaces does not imply using classes to implement those interfaces.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not true. You can still implement a type; unless the type definition uses a union operator.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type TestObj = {
x: number;
y: () => boolean;
};
// valid
class Foo implements TestObj {
x = 1;
y() { return true; }
}@christianvogt You are correct - you can use object literal type alias (e.g. type AnotherTestObj = TestObj & {
z: string;
};However, the spec is still correct in that you can't use e.g. // valid
type Bar = <T extends TestObj>(obj: T) => void;
// invalid
class Qux extends TestObj {}Still, I'd prefer using interfaces, as they describe the shape of an object (they're not a traditional OOP interface whose usage is coupled with classes). |
||
| type: string; | ||
| properties: P; | ||
| } | ||
|
|
||
| /** | ||
| * A plugin is simply a list of extensions. | ||
| * | ||
| * Plugin metadata is stored in the `package.json` file of the corresponding | ||
| * monorepo package. The `consolePlugin.entry` path should point to a module | ||
| * that exports the plugin object. | ||
| * | ||
| * ```json | ||
| * { | ||
| * "name": "@console/demo-plugin", | ||
| * "version": "0.0.0-fixed", | ||
| * // scripts, dependencies, etc. | ||
| * "consolePlugin": { | ||
| * "entry": "src/plugin.ts" | ||
| * } | ||
| * } | ||
| * ``` | ||
| * | ||
| * For better type checking and code completion, use a type parameter that | ||
| * represents the union of all the extension types consumed by the plugin: | ||
| * | ||
| * ```ts | ||
| * // packages/console-demo-plugin/src/plugin.ts | ||
| * import { Plugin } from '@console/plugin-sdk'; | ||
| * | ||
| * const plugin: Plugin<FooExtension | BarExtension> = [ | ||
| * { | ||
| * type: 'Foo', | ||
| * properties: {} // Foo extension specific properties | ||
| * }, | ||
| * { | ||
| * type: 'Bar', | ||
| * properties: {} // Bar extension specific properties | ||
| * } | ||
| * ]; | ||
| * | ||
| * export default plugin; | ||
| * ``` | ||
| */ | ||
| export type Plugin<E extends Extension<any>> = E[]; | ||
|
|
||
| /** | ||
| * A list of arbitrary plugins. | ||
| */ | ||
| export type PluginList = Plugin<Extension<any>>[]; | ||
|
|
||
| // TODO(vojtech): internal code needed by plugin SDK should be moved to console-shared package | ||
|
|
||
| export * from './nav'; | ||
| export * from './pages'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| 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[]; | ||
| } | ||
| } | ||
|
|
||
| export interface HrefProperties extends NavItemProperties { | ||
| componentProps: NavItemProperties['componentProps'] & { | ||
| href: string; | ||
| activePath?: string; | ||
| } | ||
| } | ||
|
|
||
| export interface ResourceNSProperties extends NavItemProperties { | ||
| componentProps: NavItemProperties['componentProps'] & { | ||
| resource: string; | ||
| model?: K8sKind; | ||
| } | ||
| } | ||
|
|
||
| export interface HrefNavItem extends Extension<HrefProperties> { | ||
| type: 'NavItem/Href'; | ||
| } | ||
|
|
||
| export interface ResourceNSNavItem extends Extension<ResourceNSProperties> { | ||
| type: 'NavItem/ResourceNS'; | ||
| } | ||
|
|
||
| // TODO(vojtech): add ResourceClusterNavItem | ||
| export type NavItem = HrefNavItem | ResourceNSNavItem; | ||
|
|
||
| export function isHrefNavItem(e: Extension<any>): e is HrefNavItem { | ||
| return e.type === 'NavItem/Href'; | ||
| } | ||
|
|
||
| export function isResourceNSNavItem(e: Extension<any>): e is ResourceNSNavItem { | ||
| return e.type === 'NavItem/ResourceNS'; | ||
| } | ||
|
|
||
| export function isNavItem(e: Extension<any>): e is NavItem { | ||
| return isHrefNavItem(e) || isResourceNSNavItem(e); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, this change tells Jest to transform (basically "un-ignore") any
@consolemodule paths, in addition to existinglodash-esmodule paths.More specifically, without this change, following module dependency chain:
will throw syntax errors (
export * fromstatements) sincepublic/plugins.tsimports from@console/plugin-sdkand Jest doesn't transform stuff innode_modulesby default.This is also the reason why all Console monorepo packages should use the
@consolescope in their name.