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

UIAPPS-104 Create a UI Extensions react package #94

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
14 changes: 14 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ We try to clarify the configuration that might not be immediately straight-forwa
We're setting this to all of the examples so we don't end up trying to bump their versions.
It seems like that's not the proper way to use this configuration,
but it doesn't look like there's another way to get the behavior we want.

### `linked`

We're linking the `@datadog/ui-extensions-*` packages together because we want them to all have the same version.
This is only part of the story.
`linked` does keep the versions the same,
but only if every package has a changeset.
The other part of the story is the [check fixed packages script][].
This script makes sure we should always have a changeset for all of the `@datadog/ui-extensions-*` packages if any of them changes.

If `changesets` gets a feature to have actually "fixed" packages,
we can move to using that instead of this.

[check fixed packages script]: ../scripts/check-fixed-packages-ui-extensions.js
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"changelog": "@changesets/cli/changelog",
"commit": false,
"ignore": ["datadog-app-example-*"],
"linked": [],
"linked": [["@datadog/ui-extensions-*"]],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool!

"updateInternalDependencies": "minor"
}
8 changes: 8 additions & 0 deletions .changeset/four-spoons-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@datadog/ui-extensions-react': minor
'@datadog/ui-extensions-sdk': minor
---

Create a new package for UI Extensions with the React framework.

This package provides helpers that make working with the SDK in React a bit easier.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
module.exports = {
extends: ['plugin:react-hooks/recommended'],
root: true,
rules: {
'block-scoped-var': 'error',
Expand Down Expand Up @@ -303,7 +304,9 @@ module.exports = {
'**/*.config.js',
'**/test-helpers.*',
'**/*.test-helpers.*',
'**/*test.utils.*'
'**/*.test.ts',
'**/*test.utils.*',
'scripts/*'
]
}
],
Expand Down
15 changes: 15 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ We use [`changesets`][] to help with normal package releases.
When a normal PR is merged, [`changsets`][] will keep track of any [changeset][]s in this long-running PR.
When we're ready to release some number of packages, we can merge this long-running PR and [`changesets`][] will release all packages with a [changeset][].

## `@datadog/ui-extensions-*` packages

These packages are all released at the same version.
The intention is to make it so App Developers have an easier time with updates.
If we say,
"Check out this wonderful feature in 0.36.0!"
App Developers should be able to update whatever `@datadog/ui-extensions-*` packages they have to 0.36.0 and be on their way.

The alternative would be to allow different versions for each package.
That allows more flexibility and lower version churn at the expense of confusion with version updates.
A similar example to the above might be worded like,
"Check out this wonderful feature in 0.36.0 of `@datadog/ui-extensions-sdk`, 2.3.0 of `@datadog/ui-extensions-react`, 1.46.0 of `@datadog/ui-extensions-vue`, etc."
While that's a model that many other groups of packages follow,
we're opting for keeping all the versions the same.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔊


[`auto`]: https://intuit.github.io/auto/
[`changesets`]: https://github.com/changesets/changesets
[changeset]: https://github.com/changesets/changesets/blob/main/docs/detailed-explanation.md
1 change: 1 addition & 0 deletions examples/random-dog/dog-image-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@datadog/ui-extensions-react": "0.25.0",
"@datadog/ui-extensions-sdk": "0.25.0",
"@types/node": "^14.14.14",
"@types/react": "^17.0.0",
Expand Down
10 changes: 7 additions & 3 deletions examples/random-dog/dog-image-widget/src/widget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useContext } from '@datadog/ui-extensions-react';
import {
WidgetOptionItemType,
init,
Expand All @@ -16,6 +17,7 @@ const client = init();
function Widget() {
const [breeds, setBreeds] = useState([]);
const [image, setImage] = useState(null);
const result = useContext(client);

useEffect(() => {
fetch('http://localhost:3001/breeds')
Expand Down Expand Up @@ -64,9 +66,11 @@ function Widget() {
}, []);

const cycleImage = async () => {
const breed = await client
.getContext()
.then(c => c.widget?.definition.options?.breed);
if (result.type !== 'initialized') {
return;
}

const breed = result.context.widget?.definition.options?.breed;
getImage(breed);
};

Expand Down
1 change: 1 addition & 0 deletions examples/starter-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@datadog/ui-extensions-react": "0.25.0",
"@datadog/ui-extensions-sdk": "0.25.0",
"@types/node": "^14.14.14",
"@types/react": "^17.0.0",
Expand Down
11 changes: 5 additions & 6 deletions examples/starter-kit/src/modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useContext } from '@datadog/ui-extensions-react';
import { init } from '@datadog/ui-extensions-sdk';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import './../index.css';
import React from 'react';
import ReactDOM from 'react-dom';
Expand All @@ -8,11 +9,9 @@ const client = init();

function Modal() {
const [clickCount, setClickCount] = useState(0);
const [args, setArgs] = useState<any>();

useEffect(() => {
client.getContext().then(({ args }) => setArgs(args));
}, [setArgs]);
const result = useContext(client);
const args =
result.type === 'initialized' ? result.context.args : undefined;

const onClick = () => {
setClickCount(clickCount + 1);
Expand Down
10 changes: 4 additions & 6 deletions examples/starter-kit/src/side-panel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { useContext } from '@datadog/ui-extensions-react';
import { init } from '@datadog/ui-extensions-sdk';
import { useState, useEffect } from 'react';
import './../index.css';
import React from 'react';
import ReactDOM from 'react-dom';

const client = init();

function SidePanel() {
const [args, setArgs] = useState<any>();

useEffect(() => {
client.getContext().then(({ args }) => setArgs(args));
}, [setArgs]);
const result = useContext(client);
const args =
result.type === 'initialized' ? result.context.args : undefined;

return (
<div
Expand Down
10 changes: 6 additions & 4 deletions examples/starter-kit/src/widget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useContext } from '@datadog/ui-extensions-react';
import { init, EventType } from '@datadog/ui-extensions-sdk';
import './../index.css';
import React from 'react';
Expand All @@ -13,12 +14,13 @@ const client = init();
function Widget() {
const [metric, setMetric] = useState('system.cpu.idle');
const [broadcastClickCount, setBroadcastClickCount] = useState(0);
const result = useContext(client);

useEffect(() => {
client.getContext().then(c => {
setMetric(c.widget?.definition.options?.metric);
});
if (result.type === 'initialized') {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ It kind of feels like we should take this opportunity to show some decent patterns for dealing with the other two states: initializing, and handshake failure. I didn't want to make too big of a change in case that wasn't wanted. What do y'all think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The funny thing is that when the frame is in loading or error state, the iframe is invisible so it almost doesn't matter what the app does, short of blowing up. The right pattern is honestly probably just

if result.type === 'initialized' {
  return null
}

// skip all other logic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right! Makes me wish we didn't have to do something about those other states and could just return the context only.

Maybe what would be more useful for actually rendering a component is not the hook for context, but a component that passes down the context once it's ready and only renders once it has a context. So you'd do something like:

import { WithContext } from '@datadog/ui-extensions-react';
import { Context, init } from '@datadog/ui-extensions-sdk';

type WidgetProps = {
    context: Context;
};

function Widget(props: WidgetProps) {
    // Work with the actual `Context` in `props.context`.
    
}

export default function render() {
    ReactDOM.render(
        <React.StrictMode>
            <WithContext client={client}>
                <Widget />
            </WithContext>
        </React.StrictMode>,
        document.getElementById('root')
    );
}

Or something, general idea being that the hook isn't as helpful as it could be since it's still too low-level.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe, we just don't deal with the error at all and the return type of useContext becomes Context | undefined?

setMetric(result.context.widget?.definition.options.metric);
}

useEffect(() => {
client.events.on(
EventType.DASHBOARD_CUSTOM_WIDGET_OPTIONS_CHANGE,
({ metric }) => {
Expand Down
1 change: 1 addition & 0 deletions examples/stream/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@datadog/datadog-api-client": "^1.0.0-beta.5",
"@datadog/ui-extensions-react": "0.25.0",
"@datadog/ui-extensions-sdk": "0.25.0",
"@types/bootstrap": "^5.1.4",
"@types/node": "^16.9.1",
Expand Down
23 changes: 9 additions & 14 deletions examples/stream/admin/src/account-panel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useContext } from '@datadog/ui-extensions-react';
import { useState, useEffect } from 'react';
import '../index.css';
import React from 'react';
Expand All @@ -17,21 +18,15 @@ function isRecord(args: unknown): args is Record<string, unknown> {

export default function AccountPanel() {
const [tweets, setTweets] = useState<Tweet[]>([]);
const [account, setAccount] = useState<string>('');
const result = useContext(client);
let account = '';

useEffect(() => {
client.getContext().then(({ args }) => {
if (!isRecord(args)) {
return;
}

if (typeof args.account !== 'string') {
return;
}

return setAccount(args.account);
});
}, []);
if (result.type === 'initialized') {
const args = result.context.args;
if (isRecord(args) && typeof args.account === 'string') {
account = args.account;
}
}

async function getTweets(account: string) {
const data = await get<{ tweets: Tweet[] }>(`stream/${account}`);
Expand Down
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"lint:check": "eslint '**/*.{ts,js}'",
"lint:fix": "yarn run lint:check --fix",
"lint": "yarn run lint:check && prettier --check .",
"postinstall": "patch-package",
"prepare": "yarn workspaces run prepare",
"release:canary": "auto shipit",
"release:packages": "changeset publish",
Expand All @@ -20,7 +21,9 @@
"test:example-packages:ui-extensions-sdk-version": "./scripts/check-example-packages-ui-extensions-sdk-version.js",
"test:example-packages:version": "./scripts/check-example-packages-version.js",
"test:example-packages": "yarn test:example-packages:name && yarn test:example-packages:private && yarn test:example-packages:ui-extensions-sdk-version && yarn test:example-packages:version",
"test": "yarn test:example-packages && yarn workspaces run test"
"test:fixed-packages:ui-extensions": "./scripts/check-fixed-packages-ui-extensions.js",
"test:fixed-packages": "yarn test:fixed-packages:ui-extensions",
"test": "yarn test:example-packages && yarn test:fixed-packages && yarn workspaces run test"
},
"license": "Apache-2.0",
"private": true,
Expand All @@ -31,10 +34,12 @@
],
"devDependencies": {
"@changesets/cli": "2.18.1",
"@changesets/get-release-plan": "3.0.4",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"auto": "10.32.5",
"babel-eslint": "10.1.0",
"eslint": "7.25.0",
"eslint-config-airbnb-base": "14.2.1",
"eslint-config-prettier": "8.3.0",
"eslint-config-react-app": "6.0.0",
Expand All @@ -47,11 +52,12 @@
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-no-storage": "1.0.2",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-react": "7.24.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-testing-library": "3.10.2",
"eslint": "7.25.0",
"lerna": "4.0.0",
"patch-package": "6.4.7",
"postinstall-postinstall": "2.1.0",
"prettier": "2.1.2",
"typescript": "4.5.4"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/ui-extensions-react/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
coverage
dist
Loading