Skip to content
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

Superset ObservableHQ Plugin #3

Draft
wants to merge 46 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
bd5b4ef
feat: observable plugin, take 1
rusackas May 27, 2020
11dbcc0
removing dead end test
rusackas May 27, 2020
f403b38
simplified plugin, janky thumbnails
rusackas May 27, 2020
245c18f
working URL control
rusackas May 27, 2020
b41a299
nixing console junk
rusackas May 27, 2020
186fee3
control for cells to display (static)
rusackas May 27, 2020
bd0a49b
Debugger checkbox
rusackas May 27, 2020
f7a6e1c
add ObservableWrapper component
nytai May 27, 2020
1bb96fa
small tweak, yarn lock
rusackas May 27, 2020
09fa688
disallowing "Select All" on displayCells control (it doesn't work)
rusackas May 27, 2020
f295631
allow multiple series and metrics
rusackas May 28, 2020
f61a134
rename to actual variable
nytai May 27, 2020
1a060b2
render all or selected cells
nytai May 27, 2020
01224cd
pass debug info as children
nytai May 28, 2020
29e910e
pull styles up
nytai May 28, 2020
d593786
Selector for data insertion cell, bugfix for control selectors,
rusackas May 28, 2020
11e071e
observableWrapper -> observableLoader
nytai May 28, 2020
913e08a
hook up dataInjectionCell
nytai May 28, 2020
6049690
fix types and add debug header
nytai May 28, 2020
7970ca5
rename cells
nytai May 28, 2020
e28f704
moving notebook above debug junk
rusackas May 28, 2020
3d1b416
pass down width and height
nytai May 28, 2020
efde07b
oops, typo
nytai May 28, 2020
1652314
enable Select All in series control
rusackas May 28, 2020
9c283b5
allowing freeform cells to display
rusackas May 28, 2020
c83851b
grabbing/broadcasting cell names
rusackas May 29, 2020
5510449
making ts/linters happy
rusackas May 29, 2020
565dcbf
more tsignore
rusackas May 29, 2020
d13856a
fancier thumbs
rusackas Jun 1, 2020
f5de477
redux magic :rainbow:
rusackas Jun 1, 2020
3f16f5a
adding react-redux types
rusackas Jun 4, 2020
b662838
removing unused import
rusackas Jun 4, 2020
90ee2f2
yarn lock
rusackas Jun 4, 2020
3693083
shutting up typescript with ts-nocheck (fix this!)
rusackas Jun 4, 2020
af2b73b
more ts-ignore (temp)
rusackas Jun 5, 2020
fd8b3b1
lock file
rusackas Jun 5, 2020
9c47461
removing extraneous debug info
rusackas Jun 5, 2020
9502c53
react-redux as peer dep
nytai Jun 5, 2020
5d9d554
yarn lock
rusackas Jun 5, 2020
57bb0ab
flattened/simplified file structure.
rusackas Jun 5, 2020
cd2335c
clean up gather cell names and cell redefines
nytai Jun 8, 2020
08c5eb2
fix test
nytai Jun 8, 2020
8a39fae
lint
nytai Jun 8, 2020
cf434a1
add missing fields and remove ts-ignore
villebro Jun 9, 2020
744f20b
add test for observable:
pkdotson Jun 8, 2020
2641ce1
add test for observable and dependencies
pkdotson Jun 9, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions plugins/plugin-chart-observable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## @superset-ui/plugin-chart-observable

[![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-observable.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/plugin-chart-observable.svg?style=flat-square)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-plugin-chart-observable&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-plugin-chart-observable)

This plugin provides Word Cloud for Superset.

### Usage

Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to
lookup this chart throughout the app.

```js
import ObservableChartPlugin from '@superset-ui/legacy-plugin-chart-observable';

new ObservableChartPlugin().configure({ key: 'observable' }).register();
```

Then use it via `SuperChart`. See
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-observable)
for more details.

```js
<SuperChart
chartType="observable"
width={600}
height={600}
formData={...}
queryData={{
data: {...},
}}
/>
```
52 changes: 52 additions & 0 deletions plugins/plugin-chart-observable/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@superset-ui/plugin-chart-observable",
"version": "0.13.21",
"description": "Superset Chart Plugin - Observable",
"sideEffects": [
"*.css"
],
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@observablehq/runtime": "^4.7.0",
"@types/d3-cloud": "^1.2.1",
"@types/d3-scale": "^2.0.2",
"@types/react": "^16.3.0",
"@types/react-redux": "^7.1.9",
"d3-cloud": "^1.2.5",
"d3-scale": "^3.0.1",
"emotion-theming": "^10.0.27",
"encodable": "^0.3.3",
"redux-test-utils": "^1.0.0"
},
"peerDependencies": {
"@superset-ui/chart": "^0.13.0",
"@superset-ui/color": "^0.13.0",
"@superset-ui/query": "^0.13.0",
"@superset-ui/style": "^0.13.11",
"@superset-ui/translation": "^0.13.0",
"@superset-ui/validator": "^0.13.0",
"react": "^16.3.0",
"react-redux": "^5.0.2"
}
}
11 changes: 11 additions & 0 deletions plugins/plugin-chart-observable/src/buildQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { buildQueryContext } from '@superset-ui/query';
import { ObservableFormData } from './types';

export default function buildQuery(formData: ObservableFormData) {
// Set the single QueryObject's groupby field with series in formData
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
},
]);
}
56 changes: 56 additions & 0 deletions plugins/plugin-chart-observable/src/chart/Observable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { PlainObject } from 'encodable';
import { SupersetThemeProps } from '@superset-ui/style';
import ObservableLoader from './ObservableLoader';

/**
* These props should be stored when saving the chart.
*/
export interface ObservableVisualProps {
observableUrl: string;
displayedCells: string[];
dataInjectionCell: string;
showDebug: boolean;
}

export interface ObservableProps extends ObservableVisualProps {
data: PlainObject[];
height: number;
width: number;
}

class Observable extends React.PureComponent<ObservableProps & SupersetThemeProps> {
render() {
const {
width,
height,
data,
observableUrl,
displayedCells,
dataInjectionCell,
showDebug,
} = this.props;

return (
<div>
<ObservableLoader
observableUrl={observableUrl}
data={data}
displayedCells={displayedCells}
dataInjectionCell={dataInjectionCell}
width={width}
height={height}
>
{showDebug && (
<div>
<h2>Data</h2>
<pre>{JSON.stringify(data, undefined, 2)}</pre>
</div>
)}
</ObservableLoader>
</div>
);
}
}

export default Observable;
114 changes: 114 additions & 0 deletions plugins/plugin-chart-observable/src/chart/ObservableLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { Component } from 'react';
// eslint-disable-next-line import/no-unresolved
import { connect } from 'react-redux';
// @ts-ignore
import { Runtime, Inspector } from '@observablehq/runtime';

interface Props {
observableUrl: string;
data: any;
displayedCells: string[];
dataInjectionCell: string;
broadcastCells: Function;
selectControlOptions: string[];
width: number;
height: number;
children: React.ReactNode;
}

class ObservableLoader extends Component<Props> {
notebookWrapperRef = React.createRef<HTMLDivElement>();

displayRefs: { [key: string]: HTMLDivElement | null } = {};

notebook = null;

dataCell = {
value: null,
};

get notebookURL() {
const notebookName = this.props.observableUrl.replace(/.*:\/\/observablehq.com\//gim, '');
return `https://api.observablehq.com/${notebookName}.js?v=3`;
}

componentDidMount() {
import(/* webpackIgnore: true */ this.notebookURL).then(module => {
this.notebook = module.default;

const runtime = new Runtime();
let notebookModule = null;

if (this.props.displayedCells.length === 0) {
// render the entrire notebook if no displayedCells selected
notebookModule = runtime.module(
this.notebook,
Inspector.into(this.notebookWrapperRef.current),
);
} else {
notebookModule = runtime.module(this.notebook, (name: string) => {
// render selected cells
if (this.props.displayedCells.includes(name) && this.displayRefs[name] !== null) {
return new Inspector(this.displayRefs[name]);
}
return null;
});
}

// gather cell names
// eslint-disable-next-line no-underscore-dangle
const cellNames = Array.from<any[]>(notebookModule._scope).map((item: any[]) => item[0]);
if (!this.props.selectControlOptions) {
this.props.broadcastCells(cellNames);
}

// inject the data
if (this.props.dataInjectionCell.length > 0) {
notebookModule.redefine(this.props.dataInjectionCell, [], this.props.data);
}
// define height
notebookModule.redefine('width', [], this.props.width);
});
}

render() {
const wrapperStyles = {
overflow: 'auto',
width: this.props.width,
height: this.props.height,
};
return (
<div style={wrapperStyles}>
<div ref={this.notebookWrapperRef} className="notebook-wrapper">
{this.props.displayedCells.map(name => (
<div
ref={ref => {
this.displayRefs[name] = ref;
}}
key={name}
id={`cell-${name}`}
/>
))}
</div>
{this.props.children}
</div>
);
}
}

function overwriteSelectControlOptions(selectControlOptions: string[]) {
return { type: 'OVERWRITE_SELECT_CONTROL_OPTIONS', selectControlOptions };
}

function mapSateToProps(state: any) {
return {
selectControlOptions: state.explore.selectControlOptions,
};
}

function mapDispatchToProps(dispatch: Function) {
return {
broadcastCells: (cellList: string[]) => dispatch(overwriteSelectControlOptions(cellList)),
};
}
export default connect(mapSateToProps, mapDispatchToProps)(ObservableLoader);
128 changes: 128 additions & 0 deletions plugins/plugin-chart-observable/src/controlPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// @ts-nocheck
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/translation';
import { validateNonEmpty } from '@superset-ui/validator';

// type State = unknown;

export default {
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [['series'], ['metric'], ['adhoc_filters'], ['row_limit', null]],
},
{
label: t('Options'),
expanded: true,
controlSetRows: [
[
{
name: 'observable_url',
config: {
type: 'TextControl',
label: t('Observable URL'),
description: t('URL of the Observable HQ workbook you wish to add in'),
renderTrigger: true,
clearable: true,
},
},
],
[
{
name: 'displayed_cells',
config: {
type: 'SelectControl',
multi: true,
label: t('Displayed Cells'),
default: [],
description: t(
'Select the cells from your Observable notebook that you wish to display on the Dashboard',
),
// optionRenderer: c => <ColumnOption showType column={c} />,
// valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'name',
allowAll: true,
mapStateToProps: (explore: unknown) => {
return {
options: explore.selectControlOptions
? explore.selectControlOptions.map((option: string) => ({ name: option }))
: [],
};
},
freeForm: true,
renderTrigger: true,
},
},
],
[
{
name: 'data_injection_cell',
config: {
type: 'SelectControl',
label: t('Data Injection Cell'),
default: [],
description: t(
'Select the cell from your Observable notebook into which you wish to insert your data',
),
// optionRenderer: c => <ColumnOption showType column={c} />,
// valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'name',
// allowAll: true,
mapStateToProps: (explore: unknown) => {
return {
options: explore.selectControlOptions
? explore.selectControlOptions.map((option: string) => ({ name: option }))
: [],
};
},
options: [{ name: 'observable' }, { name: 'rawData' }, { name: 'control' }],
// commaChoosesOption: false,
// freeForm: true,
renderTrigger: true,
},
},
],
[
{
name: 'show_debug',
config: {
type: 'CheckboxControl',
label: t('Debug'),
renderTrigger: true,
default: true,
description: t('Show the data used to render your Observable'),
},
},
],
],
},
],
controlOverrides: {
series: {
validators: [validateNonEmpty],
clearable: false,
multi: true,
},
metric: {
multi: true,
},
},
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading