mirror of https://github.com/grafana/grafana
AppPlugin: remove simple app from the core repo (#21526)
parent
f6130db03d
commit
c5da1864a6
@ -1,110 +0,0 @@ |
||||
// Use the real plugin_loader (stubbed by default)
|
||||
jest.unmock('app/features/plugins/plugin_loader'); |
||||
|
||||
(global as any).ace = { |
||||
define: jest.fn(), |
||||
}; |
||||
|
||||
jest.mock('app/core/core', () => { |
||||
return { |
||||
coreModule: { |
||||
directive: jest.fn(), |
||||
}, |
||||
}; |
||||
}); |
||||
|
||||
import { SystemJS } from '@grafana/runtime'; |
||||
import { AppPluginMeta, PluginMetaInfo, PluginType, PluginIncludeType, AppPlugin } from '@grafana/data'; |
||||
import { importAppPlugin } from './plugin_loader'; |
||||
|
||||
class MyCustomApp extends AppPlugin { |
||||
initWasCalled = false; |
||||
calledTwice = false; |
||||
|
||||
init(meta: AppPluginMeta) { |
||||
this.initWasCalled = true; |
||||
this.calledTwice = this.meta === meta; |
||||
} |
||||
} |
||||
|
||||
describe('Load App', () => { |
||||
const app = new MyCustomApp(); |
||||
const modulePath = 'my/custom/plugin/module'; |
||||
|
||||
beforeAll(() => { |
||||
SystemJS.set(modulePath, SystemJS.newModule({ plugin: app })); |
||||
}); |
||||
|
||||
afterAll(() => { |
||||
SystemJS.delete(modulePath); |
||||
}); |
||||
|
||||
it('should call init and set meta', async () => { |
||||
const meta: AppPluginMeta = { |
||||
id: 'test-app', |
||||
module: modulePath, |
||||
baseUrl: 'xxx', |
||||
info: {} as PluginMetaInfo, |
||||
type: PluginType.app, |
||||
name: 'test', |
||||
}; |
||||
|
||||
// Check that we mocked the import OK
|
||||
const m = await SystemJS.import(modulePath); |
||||
expect(m.plugin).toBe(app); |
||||
|
||||
const loaded = await importAppPlugin(meta); |
||||
expect(loaded).toBe(app); |
||||
expect(app.meta).toBe(meta); |
||||
expect(app.initWasCalled).toBeTruthy(); |
||||
expect(app.calledTwice).toBeFalsy(); |
||||
|
||||
const again = await importAppPlugin(meta); |
||||
expect(again).toBe(app); |
||||
expect(app.calledTwice).toBeTruthy(); |
||||
}); |
||||
}); |
||||
|
||||
import { ExampleConfigCtrl as ConfigCtrl } from 'app/plugins/app/example-app/legacy/config'; |
||||
import { AngularExamplePageCtrl } from 'app/plugins/app/example-app/legacy/angular_example_page'; |
||||
|
||||
describe('Load Legacy App', () => { |
||||
const app = { |
||||
ConfigCtrl, |
||||
AngularExamplePageCtrl, // Must match `pages.component` in plugin.json
|
||||
}; |
||||
|
||||
const modulePath = 'my/custom/legacy/plugin/module'; |
||||
|
||||
beforeAll(() => { |
||||
SystemJS.set(modulePath, SystemJS.newModule(app)); |
||||
}); |
||||
|
||||
afterAll(() => { |
||||
SystemJS.delete(modulePath); |
||||
}); |
||||
|
||||
it('should call init and set meta for legacy app', async () => { |
||||
const meta: AppPluginMeta = { |
||||
id: 'test-app', |
||||
module: modulePath, |
||||
baseUrl: 'xxx', |
||||
info: {} as PluginMetaInfo, |
||||
type: PluginType.app, |
||||
name: 'test', |
||||
includes: [ |
||||
{ |
||||
type: PluginIncludeType.page, |
||||
name: 'Example Page', |
||||
component: 'AngularExamplePageCtrl', |
||||
role: 'Viewer', |
||||
addToNav: false, |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
const loaded = await importAppPlugin(meta); |
||||
expect(loaded).toHaveProperty('angularPages'); |
||||
expect(loaded.angularPages).toHaveProperty('AngularExamplePageCtrl', AngularExamplePageCtrl); |
||||
}); |
||||
}); |
@ -1,103 +0,0 @@ |
||||
// Libraries
|
||||
import React, { PureComponent } from 'react'; |
||||
|
||||
// Types
|
||||
import { NavModelItem, AppRootProps } from '@grafana/data'; |
||||
|
||||
interface Props extends AppRootProps {} |
||||
|
||||
const TAB_ID_A = 'A'; |
||||
const TAB_ID_B = 'B'; |
||||
const TAB_ID_C = 'C'; |
||||
|
||||
export class ExampleRootPage<ExampleAppSettings> extends PureComponent<Props> { |
||||
constructor(props: Props) { |
||||
super(props); |
||||
} |
||||
|
||||
componentDidMount() { |
||||
this.updateNav(); |
||||
} |
||||
|
||||
componentDidUpdate(prevProps: Props) { |
||||
if (this.props.query !== prevProps.query) { |
||||
if (this.props.query.tab !== prevProps.query.tab) { |
||||
this.updateNav(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
updateNav() { |
||||
const { path, onNavChanged, query, meta } = this.props; |
||||
|
||||
const tabs: NavModelItem[] = []; |
||||
tabs.push({ |
||||
text: 'Tab A', |
||||
icon: 'fa fa-fw fa-file-text-o', |
||||
url: path + '?tab=' + TAB_ID_A, |
||||
id: TAB_ID_A, |
||||
}); |
||||
tabs.push({ |
||||
text: 'Tab B', |
||||
icon: 'fa fa-fw fa-file-text-o', |
||||
url: path + '?tab=' + TAB_ID_B, |
||||
id: TAB_ID_B, |
||||
}); |
||||
tabs.push({ |
||||
text: 'Tab C', |
||||
icon: 'fa fa-fw fa-file-text-o', |
||||
url: path + '?tab=' + TAB_ID_C, |
||||
id: TAB_ID_C, |
||||
}); |
||||
|
||||
// Set the active tab
|
||||
let found = false; |
||||
const selected = query.tab || TAB_ID_B; |
||||
for (const tab of tabs) { |
||||
tab.active = !found && selected === tab.id; |
||||
if (tab.active) { |
||||
found = true; |
||||
} |
||||
} |
||||
if (!found) { |
||||
tabs[0].active = true; |
||||
} |
||||
|
||||
const node = { |
||||
text: 'This is the Page title', |
||||
img: meta.info.logos.large, |
||||
subTitle: 'subtitle here', |
||||
url: path, |
||||
children: tabs, |
||||
}; |
||||
|
||||
// Update the page header
|
||||
onNavChanged({ |
||||
node: node, |
||||
main: node, |
||||
}); |
||||
} |
||||
|
||||
render() { |
||||
const { path, query, meta } = this.props; |
||||
|
||||
return ( |
||||
<div> |
||||
QUERY: <pre>{JSON.stringify(query)}</pre> |
||||
<br /> |
||||
<ul> |
||||
<li> |
||||
<a href={path + '?x=1'}>111</a> |
||||
</li> |
||||
<li> |
||||
<a href={path + '?x=AAA'}>AAA</a> |
||||
</li> |
||||
<li> |
||||
<a href={path + '?x=1&y=2&y=3'}>ZZZ</a> |
||||
</li> |
||||
</ul> |
||||
<pre>{JSON.stringify(meta.jsonData)}</pre> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -1,4 +0,0 @@ |
||||
# Example App - Native Plugin |
||||
|
||||
This is an example app. It has no real use other than making sure external apps are supported. |
||||
|
@ -1,26 +0,0 @@ |
||||
// Libraries
|
||||
import React, { PureComponent } from 'react'; |
||||
|
||||
// Types
|
||||
import { PluginConfigPageProps, AppPluginMeta } from '@grafana/data'; |
||||
import { ExampleAppSettings } from '../types'; |
||||
|
||||
interface Props extends PluginConfigPageProps<AppPluginMeta<ExampleAppSettings>> {} |
||||
|
||||
export class ExamplePage1 extends PureComponent<Props> { |
||||
constructor(props: Props) { |
||||
super(props); |
||||
} |
||||
|
||||
render() { |
||||
const { query } = this.props; |
||||
|
||||
return ( |
||||
<div> |
||||
11111111111111111111111111111111 |
||||
<pre>{JSON.stringify(query)}</pre> |
||||
11111111111111111111111111111111 |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -1,26 +0,0 @@ |
||||
// Libraries
|
||||
import React, { PureComponent } from 'react'; |
||||
|
||||
// Types
|
||||
import { PluginConfigPageProps, AppPluginMeta } from '@grafana/data'; |
||||
import { ExampleAppSettings } from '../types'; |
||||
|
||||
interface Props extends PluginConfigPageProps<AppPluginMeta<ExampleAppSettings>> {} |
||||
|
||||
export class ExamplePage2 extends PureComponent<Props> { |
||||
constructor(props: Props) { |
||||
super(props); |
||||
} |
||||
|
||||
render() { |
||||
const { query } = this.props; |
||||
|
||||
return ( |
||||
<div> |
||||
22222222222222222222222222222222 |
||||
<pre>{JSON.stringify(query)}</pre> |
||||
22222222222222222222222222222222 |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -1,110 +0,0 @@ |
||||
{ |
||||
"__inputs": [], |
||||
"__requires": [ |
||||
{ |
||||
"type": "grafana", |
||||
"id": "grafana", |
||||
"name": "Grafana", |
||||
"version": "6.2.0-pre" |
||||
}, |
||||
{ |
||||
"type": "panel", |
||||
"id": "singlestat2", |
||||
"name": "Singlestat (react)", |
||||
"version": "" |
||||
} |
||||
], |
||||
"annotations": { |
||||
"list": [ |
||||
{ |
||||
"builtIn": 1, |
||||
"datasource": "-- Grafana --", |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations & Alerts", |
||||
"type": "dashboard" |
||||
} |
||||
] |
||||
}, |
||||
"editable": true, |
||||
"gnetId": null, |
||||
"graphTooltip": 0, |
||||
"id": null, |
||||
"links": [], |
||||
"panels": [ |
||||
{ |
||||
"gridPos": { |
||||
"h": 4, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 2, |
||||
"options": { |
||||
"orientation": "auto", |
||||
"sparkline": { |
||||
"fillColor": "rgba(31, 118, 189, 0.18)", |
||||
"full": false, |
||||
"lineColor": "rgb(31, 120, 193)", |
||||
"show": true |
||||
}, |
||||
"thresholds": [ |
||||
{ |
||||
"color": "green", |
||||
"index": 0, |
||||
"value": null |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"index": 1, |
||||
"value": 80 |
||||
} |
||||
], |
||||
"valueMappings": [], |
||||
"valueOptions": { |
||||
"decimals": null, |
||||
"prefix": "", |
||||
"stat": "mean", |
||||
"suffix": "", |
||||
"unit": "none" |
||||
} |
||||
}, |
||||
"pluginVersion": "6.2.0-pre", |
||||
"targets": [ |
||||
{ |
||||
"refId": "A", |
||||
"scenarioId": "random_walk_table", |
||||
"stringInput": "" |
||||
}, |
||||
{ |
||||
"refId": "B", |
||||
"scenarioId": "random_walk_table", |
||||
"stringInput": "" |
||||
} |
||||
], |
||||
"timeFrom": null, |
||||
"timeShift": null, |
||||
"title": "Panel Title", |
||||
"type": "singlestat2" |
||||
} |
||||
], |
||||
"schemaVersion": 18, |
||||
"style": "dark", |
||||
"tags": [], |
||||
"templating": { |
||||
"list": [] |
||||
}, |
||||
"time": { |
||||
"from": "now-6h", |
||||
"to": "now" |
||||
}, |
||||
"timepicker": { |
||||
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], |
||||
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] |
||||
}, |
||||
"timezone": "", |
||||
"title": "stats", |
||||
"uid": "YeBxHjzWz", |
||||
"version": 1 |
||||
} |
@ -1,83 +0,0 @@ |
||||
{ |
||||
"__inputs": [], |
||||
"__requires": [ |
||||
{ |
||||
"type": "grafana", |
||||
"id": "grafana", |
||||
"name": "Grafana", |
||||
"version": "6.2.0-pre" |
||||
}, |
||||
{ |
||||
"type": "panel", |
||||
"id": "graph2", |
||||
"name": "React Graph", |
||||
"version": "" |
||||
} |
||||
], |
||||
"annotations": { |
||||
"list": [ |
||||
{ |
||||
"builtIn": 1, |
||||
"datasource": "-- Grafana --", |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations & Alerts", |
||||
"type": "dashboard" |
||||
} |
||||
] |
||||
}, |
||||
"editable": true, |
||||
"gnetId": null, |
||||
"graphTooltip": 0, |
||||
"id": null, |
||||
"links": [], |
||||
"panels": [ |
||||
{ |
||||
"description": "", |
||||
"gridPos": { |
||||
"h": 6, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 2, |
||||
"links": [], |
||||
"targets": [ |
||||
{ |
||||
"refId": "A", |
||||
"scenarioId": "streaming_client", |
||||
"stream": { |
||||
"noise": 10, |
||||
"speed": 100, |
||||
"spread": 20, |
||||
"type": "signal" |
||||
}, |
||||
"stringInput": "" |
||||
} |
||||
], |
||||
"timeFrom": null, |
||||
"timeShift": null, |
||||
"title": "Simple dummy streaming example", |
||||
"type": "graph2" |
||||
} |
||||
], |
||||
"schemaVersion": 18, |
||||
"style": "dark", |
||||
"tags": [], |
||||
"templating": { |
||||
"list": [] |
||||
}, |
||||
"time": { |
||||
"from": "now-1m", |
||||
"to": "now" |
||||
}, |
||||
"timepicker": { |
||||
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], |
||||
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] |
||||
}, |
||||
"timezone": "", |
||||
"title": "simple streaming", |
||||
"uid": "TbbEZjzWz", |
||||
"version": 1 |
||||
} |
Before Width: | Height: | Size: 42 KiB |
@ -1,8 +0,0 @@ |
||||
|
||||
|
||||
<h3 class="page-heading"> |
||||
Example Page |
||||
</h3> |
||||
|
||||
<p>this is in angular</p> |
||||
|
@ -1,10 +0,0 @@ |
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; |
||||
|
||||
export class AngularExamplePageCtrl { |
||||
static templateUrl = 'legacy/angular_example_page.html'; |
||||
|
||||
/** @ngInject */ |
||||
constructor($scope: any, $rootScope: GrafanaRootScope) { |
||||
console.log('AngularExamplePageCtrl:', this); |
||||
} |
||||
} |
@ -1,22 +0,0 @@ |
||||
<h2>Example Application</h2> |
||||
|
||||
<p> |
||||
Angular based config: |
||||
</p> |
||||
|
||||
<div class="gf-form"> |
||||
<div class="gf-form-group"> |
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label">json Data property</span> |
||||
<input type="text" class="gf-form-input" ng-model="ctrl.appModel.jsonData.customText" > |
||||
</div> |
||||
<div class="gf-form"> |
||||
<gf-form-checkbox class="gf-form" |
||||
label="Custom Value" |
||||
checked="ctrl.appModel.jsonData.customCheckbox" |
||||
switch-class="max-width-6"></gf-form-checkbox> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -1,36 +0,0 @@ |
||||
import { PluginMeta } from '@grafana/data'; |
||||
|
||||
export class ExampleConfigCtrl { |
||||
static templateUrl = 'legacy/config.html'; |
||||
|
||||
appEditCtrl: any; |
||||
appModel: PluginMeta; |
||||
|
||||
/** @ngInject */ |
||||
constructor($scope: any, $injector: any) { |
||||
this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this)); |
||||
|
||||
// Make sure it has a JSON Data spot
|
||||
if (!this.appModel) { |
||||
this.appModel = {} as PluginMeta; |
||||
} |
||||
|
||||
// Required until we get the types sorted on appModel :(
|
||||
const appModel = this.appModel as any; |
||||
if (!appModel.jsonData) { |
||||
appModel.jsonData = {}; |
||||
} |
||||
|
||||
console.log('ExampleConfigCtrl', this); |
||||
} |
||||
|
||||
postUpdate() { |
||||
if (!this.appModel.enabled) { |
||||
console.log('Not enabled...'); |
||||
return; |
||||
} |
||||
|
||||
// TODO, can do stuff after update
|
||||
console.log('Post Update:', this); |
||||
} |
||||
} |
@ -1,29 +0,0 @@ |
||||
// Angular pages
|
||||
import { ExampleConfigCtrl } from './legacy/config'; |
||||
import { AngularExamplePageCtrl } from './legacy/angular_example_page'; |
||||
import { AppPlugin } from '@grafana/data'; |
||||
import { ExamplePage1 } from './config/ExamplePage1'; |
||||
import { ExamplePage2 } from './config/ExamplePage2'; |
||||
import { ExampleRootPage } from './ExampleRootPage'; |
||||
import { ExampleAppSettings } from './types'; |
||||
|
||||
// Legacy exports just for testing
|
||||
export { |
||||
ExampleConfigCtrl as ConfigCtrl, |
||||
AngularExamplePageCtrl, // Must match `pages.component` in plugin.json
|
||||
}; |
||||
|
||||
export const plugin = new AppPlugin<ExampleAppSettings>() |
||||
.setRootPage(ExampleRootPage) |
||||
.addConfigPage({ |
||||
title: 'Page 1', |
||||
icon: 'fa fa-info', |
||||
body: ExamplePage1, |
||||
id: 'page1', |
||||
}) |
||||
.addConfigPage({ |
||||
title: 'Page 2', |
||||
icon: 'fa fa-user', |
||||
body: ExamplePage2, |
||||
id: 'page2', |
||||
}); |
@ -1,42 +0,0 @@ |
||||
{ |
||||
"type": "app", |
||||
"name": "Example App", |
||||
"id": "example-app", |
||||
"state": "alpha", |
||||
|
||||
"info": { |
||||
"author": { |
||||
"name": "Grafana Project", |
||||
"url": "https://grafana.com" |
||||
}, |
||||
"logos": { |
||||
"small": "img/logo.png", |
||||
"large": "img/logo.png" |
||||
} |
||||
}, |
||||
|
||||
"includes": [ |
||||
{ |
||||
"type": "page", |
||||
"name": "Angular Page", |
||||
"component": "AngularExamplePageCtrl", |
||||
"role": "Viewer", |
||||
"addToNav": true, |
||||
"defaultNav": true |
||||
}, |
||||
{ |
||||
"type": "dashboard", |
||||
"name": "Streaming Example", |
||||
"path": "dashboards/streaming.json" |
||||
}, |
||||
{ |
||||
"type": "dashboard", |
||||
"name": "Lots of Stats", |
||||
"path": "dashboards/stats.json" |
||||
}, |
||||
{ |
||||
"type": "panel", |
||||
"name": "Anything -- just display?" |
||||
} |
||||
] |
||||
} |
@ -1,4 +0,0 @@ |
||||
export interface ExampleAppSettings { |
||||
customText?: string; |
||||
customCheckbox?: boolean; |
||||
} |
Loading…
Reference in new issue