├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ ├── publish-api-documentation.yml │ ├── publish-canary.yml │ └── publish-packages.yml ├── .gitignore ├── .husky └── pre-commit ├── .node-version ├── .prettierignore ├── .vscode └── extensions.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE.md ├── auto.config.ts ├── docs ├── API │ ├── .gitignore │ └── README.md └── en │ ├── faq.md │ ├── getting-started.md │ ├── programming-model.md │ └── ui-extensions-design-guidelines.md ├── examples ├── custom-widget-with-1st-party-data │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── _redirects │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── 1st-party │ │ │ └── monitor │ │ │ │ ├── index.ts │ │ │ │ └── monitor.ts │ │ ├── client │ │ │ ├── client.ts │ │ │ └── index.ts │ │ ├── controller │ │ │ ├── controller.tsx │ │ │ └── index.ts │ │ ├── custom-widget │ │ │ ├── content.tsx │ │ │ ├── custom-widget.tsx │ │ │ └── index.tsx │ │ ├── index.ts │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── custom-widget-with-3rd-party-data │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── _redirects │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── 3rd-party │ │ │ ├── login │ │ │ │ ├── index.ts │ │ │ │ └── login.tsx │ │ │ ├── post │ │ │ │ ├── index.ts │ │ │ │ └── post.ts │ │ │ └── user │ │ │ │ ├── index.ts │ │ │ │ └── user.ts │ │ ├── client │ │ │ ├── client.ts │ │ │ └── index.ts │ │ ├── controller │ │ │ ├── controller.tsx │ │ │ ├── custom-widget.ts │ │ │ └── index.ts │ │ ├── custom-widget │ │ │ ├── custom-widget.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── geomap │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── crystal-api │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── main.cr │ │ ├── shard.lock │ │ ├── shard.yml │ │ └── src │ │ │ └── geomap.cr │ ├── datadog-app │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app_manifest.json │ │ ├── package.json │ │ ├── public │ │ │ ├── _redirects │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── controller │ │ │ │ └── index.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── react-app-env.d.ts │ │ │ └── widget │ │ │ │ └── GeoMap │ │ │ │ ├── index.tsx │ │ │ │ └── widget.css │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── docker-compose-api.yml │ ├── docker-compose-dev.yml │ ├── docker-compose.yml │ ├── hero.png │ ├── request-loop.sh │ └── setup │ │ ├── Dockerfile │ │ ├── constants.js │ │ ├── createDatadogApp.js │ │ ├── createDatadogDashboard.js │ │ ├── index.js │ │ ├── package.json │ │ └── yarn.lock ├── kitchen-sink │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── _redirects │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── 1st-party │ │ │ └── monitor │ │ │ │ ├── index.ts │ │ │ │ └── monitor.ts │ │ ├── 3rd-party │ │ │ ├── host │ │ │ │ ├── host.ts │ │ │ │ └── index.ts │ │ │ ├── login │ │ │ │ ├── index.ts │ │ │ │ └── login.tsx │ │ │ ├── post │ │ │ │ ├── index.ts │ │ │ │ └── post.ts │ │ │ └── user │ │ │ │ ├── index.ts │ │ │ │ └── user.ts │ │ ├── client │ │ │ ├── client.ts │ │ │ └── index.ts │ │ ├── controller │ │ │ ├── controller.tsx │ │ │ ├── custom-widget.ts │ │ │ ├── dashboard-cog-menu.ts │ │ │ ├── index.ts │ │ │ └── widget-context-menu.ts │ │ ├── custom-widget │ │ │ ├── content.tsx │ │ │ ├── custom-widget.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── modal │ │ │ ├── index.ts │ │ │ └── modal.tsx │ │ ├── react-app-env.d.ts │ │ └── side-panel │ │ │ ├── index.ts │ │ │ └── side-panel.tsx │ └── tsconfig.json ├── random-dog │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── docker-compose-dev.yml │ ├── docker-compose.yml │ ├── dog-api │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── yarn.lock │ ├── dog-image-widget │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app_manifest.json │ │ ├── package.json │ │ ├── public │ │ │ ├── _redirects │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── controller │ │ │ │ └── index.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── react-app-env.d.ts │ │ │ └── widget │ │ │ │ ├── index.tsx │ │ │ │ └── widget.css │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── hero.png │ └── setup │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── constants.js │ │ ├── createDatadogApp.js │ │ ├── createDatadogDashboard.js │ │ ├── index.js │ │ ├── package.json │ │ ├── yarn-error.log │ │ └── yarn.lock ├── redis │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── datadog-app │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── controller │ │ │ │ └── index.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── logo.svg │ │ │ ├── modals │ │ │ │ └── KeySearch │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ ├── reportWebVitals.js │ │ │ ├── setupTests.js │ │ │ ├── sidePanels │ │ │ │ └── KeyInfo │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ └── widgets │ │ │ │ ├── ClientsList │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ │ └── KeySearch │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ └── yarn.lock │ ├── datadog │ │ ├── Dockerfile │ │ └── conf.d │ │ │ └── redisdb.yaml │ ├── docker-compose-dev.yml │ ├── docker-compose.yml │ ├── flask-app │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── app.py │ │ └── requirements.txt │ ├── products-api │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── db │ │ │ │ ├── index.js │ │ │ │ └── seed.js │ │ │ ├── models │ │ │ │ ├── product.js │ │ │ │ └── user.js │ │ │ └── routes │ │ │ │ ├── products.js │ │ │ │ └── users.js │ │ └── yarn.lock │ ├── redis-app-hero.png │ └── setup │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── constants.js │ │ ├── createDatadogApp.js │ │ ├── createDatadogDashboard.js │ │ ├── index.js │ │ ├── package.json │ │ └── yarn.lock ├── sentiment │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── app-manifest.json │ ├── docs │ │ ├── main-image.png │ │ ├── sentiment-video.mp4 │ │ ├── step-1.png │ │ ├── step-2.png │ │ ├── step-3.png │ │ ├── step-4.png │ │ ├── step-5.png │ │ ├── step-6.png │ │ └── step-7.png │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── controller │ │ ├── dashboard-cog-menu.js │ │ ├── index.js │ │ └── modal.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── modal │ │ └── index.js │ │ ├── reportWebVitals.js │ │ ├── setupTests.js │ │ ├── side-panel │ │ ├── Sentiment.js │ │ └── index.js │ │ ├── tweets.js │ │ └── widget │ │ ├── Tweets.js │ │ ├── index.js │ │ └── widget.css ├── slice-pizza │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── datadog-app │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── public │ │ │ ├── _redirects │ │ │ ├── favicon.ico │ │ │ ├── img │ │ │ │ ├── header.png │ │ │ │ ├── logo.jpeg │ │ │ │ ├── logo.png │ │ │ │ ├── pizza-item.jpg │ │ │ │ ├── pizza__diavolo.png │ │ │ │ ├── pizza__harissa_lamb.png │ │ │ │ ├── pizza__mushrooms.png │ │ │ │ ├── pizza__sausage_peppers.png │ │ │ │ ├── pizza__standard.png │ │ │ │ ├── pizza__tomato.png │ │ │ │ └── widget.svg │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── components │ │ │ │ ├── OrderSuccess.tsx │ │ │ │ ├── OrderSummary.tsx │ │ │ │ ├── PizzaInput.tsx │ │ │ │ ├── PizzasList.tsx │ │ │ │ ├── SignInForm.tsx │ │ │ │ └── SignUpForm.tsx │ │ │ ├── config.js │ │ │ ├── controller │ │ │ │ └── index.ts │ │ │ ├── img │ │ │ │ ├── pizza-bg.jpg │ │ │ │ └── pizza-maker.jpg │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── modal │ │ │ │ └── SlicePizza │ │ │ │ │ └── index.tsx │ │ │ ├── react-app-env.d.ts │ │ │ ├── theme │ │ │ │ └── index.ts │ │ │ ├── types │ │ │ │ ├── Order.ts │ │ │ │ ├── Pizza.ts │ │ │ │ ├── Token.ts │ │ │ │ └── User.ts │ │ │ ├── utils │ │ │ │ └── currency.ts │ │ │ └── widget │ │ │ │ └── SlicePizza │ │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── docker-compose-dev.yml │ ├── docker-compose.yml │ ├── hero.png │ ├── proxy │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── app.py │ │ └── requirements.txt │ ├── scripts │ │ └── bootstrap.sh │ ├── setup │ │ ├── Dockerfile │ │ ├── constants.js │ │ ├── createDatadogApp.js │ │ ├── index.js │ │ ├── package.json │ │ └── yarn.lock │ └── slice-life-pizzeria │ │ ├── .data │ │ ├── menu │ │ │ └── menu.json │ │ ├── tokens │ │ │ └── .gitignore │ │ └── users │ │ │ └── .gitignore │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── example.config.js │ │ ├── index.js │ │ ├── lib │ │ ├── data.js │ │ ├── helpers.js │ │ └── routesAndHandlers.js │ │ └── public │ │ ├── Slice-Life-Pizzeria.postman_collection.json │ │ ├── email-example.jpg │ │ └── pizza_icon15.png ├── starter-kit │ ├── .gitignore │ ├── .prettierignore │ ├── LICENSE │ ├── README.md │ ├── app_manifest.json │ ├── package.json │ ├── public │ │ ├── _redirects │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── controller │ │ │ ├── dashboard-cog-menu.ts │ │ │ ├── index.ts │ │ │ ├── modal.ts │ │ │ └── widget-ctx-menu.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── modal │ │ │ └── index.tsx │ │ ├── react-app-env.d.ts │ │ ├── side-panel │ │ │ └── index.tsx │ │ └── widget │ │ │ ├── index.tsx │ │ │ └── widget.css │ └── tsconfig.json ├── stream │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── admin │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── prettier.config.js │ │ ├── public │ │ │ └── index.html │ │ ├── src │ │ │ ├── account-panel │ │ │ │ └── index.tsx │ │ │ ├── api.ts │ │ │ ├── auth-redirect │ │ │ │ └── index.tsx │ │ │ ├── blocklist-modal │ │ │ │ └── index.tsx │ │ │ ├── client.ts │ │ │ ├── controller │ │ │ │ ├── index.ts │ │ │ │ └── widget-ctx-menu.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── react-app-env.d.ts │ │ │ ├── widget │ │ │ │ ├── index.tsx │ │ │ │ └── widget.css │ │ │ └── workshop.ts │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── docker-compose-dev.yml │ ├── docker-compose.yml │ ├── server │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── index.js │ │ ├── package.json │ │ ├── stream.js │ │ ├── telemetry.js │ │ ├── users.js │ │ └── yarn.lock │ ├── setup │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── constants.js │ │ ├── create-app.js │ │ ├── create-dashboard.js │ │ ├── package.json │ │ ├── setup-workshop.js │ │ └── yarn.lock │ ├── stream.png │ └── ui │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── public │ │ └── index.html │ │ ├── src │ │ ├── Login.tsx │ │ ├── Stream.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ │ ├── tsconfig.json │ │ └── yarn.lock └── widget-context-menu-with-3rd-party-data │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── package.json │ ├── public │ ├── _redirects │ ├── favicon.ico │ ├── index.html │ └── robots.txt │ ├── src │ ├── 3rd-party │ │ ├── host │ │ │ ├── host.ts │ │ │ └── index.ts │ │ ├── login │ │ │ ├── index.ts │ │ │ └── login.tsx │ │ └── user │ │ │ ├── index.ts │ │ │ └── user.ts │ ├── client │ │ ├── client.ts │ │ └── index.ts │ ├── controller │ │ ├── controller.tsx │ │ ├── index.ts │ │ └── widget-context-menu.ts │ ├── index.ts │ ├── modal │ │ ├── index.ts │ │ └── modal.tsx │ └── react-app-env.d.ts │ └── tsconfig.json ├── lerna.json ├── package.json ├── packages ├── create-app │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── DESIGN.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.acceptance.sh │ │ ├── index.ts │ │ ├── initialize.ts │ │ └── template │ │ │ ├── .gitignore │ │ │ ├── .prettierignore │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── public │ │ │ ├── _redirects.handlebars │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ └── robots.txt │ │ │ ├── src │ │ │ ├── controller │ │ │ │ ├── dashboard-cog-menu.ts │ │ │ │ ├── index.ts.handlebars │ │ │ │ ├── modal.ts │ │ │ │ └── widget-context-menu.ts │ │ │ ├── custom-widget │ │ │ │ ├── custom-widget.css │ │ │ │ └── index.tsx │ │ │ ├── index.css │ │ │ ├── index.tsx.handlebars │ │ │ ├── modal │ │ │ │ └── index.tsx │ │ │ ├── react-app-env.d.ts │ │ │ └── side-panel │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ └── tsconfig.json ├── framepost │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── __tests__ │ │ ├── child.ts │ │ └── parent.ts │ ├── index.html │ ├── jest.config.js │ ├── package.json │ ├── prettier.config.js │ ├── sandbox │ │ ├── child │ │ │ └── index.html │ │ └── parent │ │ │ └── index.html │ ├── scripts │ │ ├── .eslintrc.js │ │ └── sandbox.js │ ├── src │ │ ├── child.ts │ │ ├── constants.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── parent.ts │ │ ├── profiler.ts │ │ ├── shared.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── webpack.config.js ├── ui-extensions-react │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── use-context.test.ts │ │ ├── use-context.ts │ │ ├── use-custom-widget-option-boolean.test.ts │ │ ├── use-custom-widget-option-boolean.ts │ │ ├── use-custom-widget-option-string.test.ts │ │ ├── use-custom-widget-option-string.ts │ │ ├── use-custom-widget-option.test.ts │ │ ├── use-custom-widget-option.ts │ │ ├── use-template-variable.test.ts │ │ └── use-template-variable.ts │ ├── tsconfig.json │ └── webpack.config.js └── ui-extensions-sdk │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.html │ ├── jest.config.js │ ├── package.json │ ├── sandbox │ ├── child │ │ └── index.html │ └── parent │ │ └── index.html │ ├── scripts │ ├── .eslintrc.js │ └── sandbox.js │ ├── src │ ├── api │ │ ├── api.test.ts │ │ └── api.ts │ ├── auth │ │ └── auth.ts │ ├── client │ │ ├── client.test.ts │ │ └── client.ts │ ├── config │ │ ├── config.test.ts │ │ └── config.ts │ ├── constants.ts │ ├── dashboard │ │ ├── dashboard-cog-menu │ │ │ ├── dashboard-cog-menu.test.ts │ │ │ └── dashboard-cog-menu.ts │ │ ├── dashboard-custom-widget │ │ │ ├── dashboard-custom-widget.test.ts │ │ │ └── dashboard-custom-widget.ts │ │ └── dashboard.ts │ ├── events │ │ ├── events.test.ts │ │ └── events.ts │ ├── features │ │ ├── dashboard-cog-menu.ts │ │ ├── dashboard-custom-widget.ts │ │ ├── index.ts │ │ ├── modals.ts │ │ ├── side-panels.ts │ │ └── widget-context-menu.ts │ ├── index.test.ts │ ├── index.ts │ ├── location │ │ └── location.ts │ ├── modal │ │ ├── modal.test.ts │ │ └── modal.ts │ ├── notification │ │ ├── notification.test.ts │ │ └── notification.ts │ ├── shared │ │ └── feature-client.ts │ ├── side-panel │ │ ├── side-panel.test.ts │ │ └── side-panel.ts │ ├── types.ts │ ├── utils │ │ ├── logger.ts │ │ ├── security.test.ts │ │ ├── security.ts │ │ ├── testUtils.ts │ │ ├── utils.test.ts │ │ └── utils.ts │ └── widget-context-menu │ │ ├── widget-context-menu.test.ts │ │ └── widget-context-menu.ts │ ├── tsconfig.json │ └── webpack.config.js ├── patches ├── @changesets+assemble-release-plan+5.0.3.patch └── README.md ├── prettier.config.js ├── scripts ├── check-example-packages-name.js ├── check-example-packages-private.js ├── check-example-packages-ui-extensions-sdk-version.js ├── check-example-packages-version.js ├── check-fixed-packages-ui-extensions.js ├── husky-avoid-prepare-in-ci.js └── package-info.js ├── tsconfig.base.json ├── typedoc.js └── yarn.lock /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.3/schema.json", 3 | "access": "public", 4 | "baseBranch": "master", 5 | "changelog": "@changesets/cli/changelog", 6 | "commit": false, 7 | "ignore": ["datadog-app-example-*"], 8 | "linked": [["@datadog/ui-extensions-*"]], 9 | "updateInternalDependencies": "minor" 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.less] 2 | indent_size = 4 3 | indent_style = space 4 | [*.js] 5 | indent_size = 4 6 | indent_style = space 7 | [*.jsx] 8 | indent_size = 4 9 | indent_style = space 10 | [*.ts] 11 | indent_size = 4 12 | indent_style = space 13 | [*.tsx] 14 | indent_size = 4 15 | indent_style = space 16 | [*.mdx] 17 | indent_size = 4 18 | indent_style = space 19 | [Rakefile] 20 | indent_size = 2 21 | indent_style = space 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/bin 2 | **/build 3 | **/CHANGELOG.md 4 | **/dist 5 | CHANGELOG.md 6 | docs 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Motivation 4 | 5 | - What is the motivation / context for this change? (Text, screenshot, JIRA ticket, Github Issue) 6 | 7 | 8 | 9 | ## Changes 10 | 11 | - What does this change? (Link to Github Issue if applicable, e.g. #44) 12 | 13 | 14 | 15 | 16 | | Before | After | 17 | | ------------------------------ | ---------------------------- | 18 | | ![Before alt text](Before URL) | ![After alt text](After URL) | 19 | 20 | ## Testing 21 | 22 | 23 | 24 | ## Releases 25 | 26 | 27 | 28 | 29 | Choose one: 30 | 31 | - [ ] No release is necessary. 32 | If you're only updating examples/documentation, this is likely what you want. 33 | - [ ] All packages that need a release have a changeset. 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | env: 11 | CI: true 12 | 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v2 16 | - name: install node 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 20.10.0 20 | - name: install 21 | run: yarn 22 | - name: lint 23 | run: yarn lint 24 | - name: build 25 | run: yarn build 26 | - name: test 27 | run: yarn test 28 | -------------------------------------------------------------------------------- /.github/workflows/publish-api-documentation.yml: -------------------------------------------------------------------------------- 1 | name: Publish API documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 20.10.0 19 | 20 | - name: Install dependencies 21 | run: yarn install 22 | 23 | - name: Build all packages 24 | run: yarn build 25 | 26 | - name: Build API documentation 27 | run: yarn docs 28 | 29 | - name: Deploy to GitHub Pages 30 | uses: JamesIves/github-pages-deploy-action@v4.2.2 31 | with: 32 | # The branch the action should deploy to. 33 | branch: gh-pages 34 | # The directory the action should deploy. 35 | folder: docs/API/packages 36 | git-config-name: github-actions[bot] 37 | git-config-email: github-actions[bot]@users.noreply.github.com 38 | -------------------------------------------------------------------------------- /.github/workflows/publish-canary.yml: -------------------------------------------------------------------------------- 1 | name: Publish canary package 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'gh-pages' 6 | - master 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | if: "!contains(github.event.head_commit.message, 'skip canary')" 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Prepare repository 15 | run: git fetch --unshallow --tags 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 20.10.0 19 | registry-url: 'https://registry.npmjs.org' 20 | scope: '@datadog' 21 | - run: yarn 22 | - run: yarn build 23 | - name: Add auth credentials for lerna 24 | run: npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN} 25 | env: 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | - run: yarn release:canary 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .DS_Store 5 | .vscode/settings.json 6 | .idea -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20.10.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/bin 2 | **/build 3 | **/CHANGELOG.md 4 | **/dist 5 | CHANGELOG.md 6 | docs 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "EditorConfig.EditorConfig", 6 | "arcanis.vscode-zipfs" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document provides some basic guidelines for contributing to this repository. 4 | To propose improvements, feel free to submit a pull request. 5 | 6 | ## Pull Requests 7 | 8 | We use [`changesets`][] to manage releases. 9 | If you're making a change, and you'd like it to be released, please create a [changeset][] in your PR. 10 | You can create a [changeset][] with the following (interactive) command: 11 | 12 | ```Console 13 | $ yarn changeset add 14 | ``` 15 | 16 | This will walk you through all the steps you need to take to create your [changeset][]. 17 | 18 | ### When to create a [changeset][]? 19 | 20 | The first thing to remember is that [not every change requires a changeset](https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md#not-every-change-requires-a-changeset). 21 | It's perfectly fine to make some small change to fix a typo or clean up whitespace or something else without making a [changeset][]. 22 | Adding or updating an example is also a fine time to skip adding a [changeset][]. 23 | 24 | For the most part, we want to add a [changeset][] when we want people to get new releases of packages. 25 | If there's a bug fix, new feature, or something along those lines for a package, we almost assuredly want to have an accompanying [changeset][]. 26 | 27 | [`changesets`]: https://github.com/changesets/changesets 28 | [changeset]: https://github.com/changesets/changesets/blob/main/docs/detailed-explanation.md 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Datadog Apps 2 | 3 | Datadog Apps allow third party developers to extend the native functionality offered by Datadog. This initiative is in a private beta release, please contact Datadog support to request access. 4 | 5 | ## Documentation 6 | 7 | - [Getting Started](docs/en/getting-started.md) 8 | - [Programming Model](docs/en/programming-model.md) 9 | - [Frequently Asked Questions](docs/en/faq.md) 10 | - [UI-Extension Design Guidelines](docs/en/ui-extensions-design-guidelines.md) 11 | - [Datadog API Reference](https://docs.datadoghq.com/api/latest/) 12 | 13 | - Note: the TypeScript examples in the Datadog API Reference and the [`@datadog/datadog-api-client`](https://www.npmjs.com/package/@datadog/datadog-api-client) are currently Node.js-only, while Datadog Apps are browser-only. The API Reference is useful to understand the Datadog API, but the examples will need some conversion for the browser. 14 | 15 | We are working on updating the [`@datadog/datadog-api-client`](https://www.npmjs.com/package/@datadog/datadog-api-client) to work seamlessly with the [`@datadog/ui-extensions-sdk`](https://www.npmjs.com/package/@datadog/ui-extensions-sdk). In the mean-time, we recommend accessing the Datadog API with the [`@datadog/ui-extensions-sdk` directly](docs/en/programming-model.md#accessing-the-api-through-the-sdk). 16 | 17 | ## Examples 18 | 19 | - [Starter Kit](examples/starter-kit) 20 | 21 | ## Code resources 22 | 23 | - [SDK](https://github.com/Datadog/ui-extensions-sdk) 24 | -------------------------------------------------------------------------------- /auto.config.ts: -------------------------------------------------------------------------------- 1 | // `auto` doesn't get resolved properly for `eslint`. 2 | // This seems to happen because it's `main` field lists a file that doesn't exist. 3 | // If we find a way to address that, we can remove this disable. 4 | // eslint-disable-next-line import/no-unresolved 5 | import { AutoRc } from 'auto'; 6 | 7 | export default function rc(): AutoRc { 8 | return { 9 | /** 10 | * The `git` author used for changelog PRs. 11 | * Object syntax not permitted in TypeScript yet. 12 | * https://intuit.github.io/auto/docs/configuration/autorc#author. 13 | */ 14 | author: 15 | 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>', 16 | 17 | plugins: [ 18 | /** 19 | * The `npm` plugin allows us to publish "canary" packages to `npm`. 20 | * https://intuit.github.io/auto/docs/generated/npm 21 | */ 22 | 'npm' 23 | ] 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /docs/API/.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | -------------------------------------------------------------------------------- /docs/API/README.md: -------------------------------------------------------------------------------- 1 | # Datadog Apps API Documentation 2 | 3 | This is the API documentation for Datadog Apps packages. 4 | If you'd like to get started with Datadog Apps, 5 | see the [official docs][]. 6 | 7 | [official docs]: https://docs.datadoghq.com/developers/datadog_apps 8 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/README.md: -------------------------------------------------------------------------------- 1 | # Datadog App 2 | 3 | ## Custom widget with 1st-party data 4 | 5 | This App provides a custom widget that interacts with 1st-party Datadog data. 6 | It can be used as a starting point for building out a real-world App on the Datadog Developer Platform. 7 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/public/_redirects: -------------------------------------------------------------------------------- 1 | /custom-widget /index.html 200 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/custom-widget-with-1st-party-data/public/favicon.ico -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/1st-party/monitor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './monitor'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/1st-party/monitor/monitor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An incomplete representation of 1st-party Datadog monitor data. 3 | */ 4 | export type Monitor = { 5 | id: number; 6 | name: string; 7 | }; 8 | 9 | function isRecord(args: unknown): args is Record { 10 | return ( 11 | typeof args === 'object' && args != null && args.constructor === Object 12 | ); 13 | } 14 | 15 | function parseMonitor(monitor: unknown): Monitor | undefined { 16 | if (!isRecord(monitor)) { 17 | return; 18 | } 19 | if (typeof monitor.id !== 'number') { 20 | return; 21 | } 22 | if (typeof monitor.name !== 'string') { 23 | return; 24 | } 25 | 26 | return { 27 | id: monitor.id, 28 | name: monitor.name 29 | }; 30 | } 31 | 32 | /** 33 | * We do a bit of work to make sure that the API response we get back is the correct shape. 34 | * 35 | * @param response The API response from the SDK. 36 | * @returns The parsed {@link Monitor}. 37 | */ 38 | export function parseMonitors(response: unknown): Monitor[] { 39 | if (!Array.isArray(response)) { 40 | return []; 41 | } 42 | 43 | return response 44 | .map(parseMonitor) 45 | .filter((monitor?: Monitor): monitor is Monitor => { 46 | return monitor != null; 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/client/client.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | 3 | /** 4 | * We initialize the {@link DDClient} in one place and use it throughout the App. 5 | * Having the {@link DDClient} initialized in one place helps centralize auth logic and provide better type inference. 6 | */ 7 | export const client = init(); 8 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/controller/controller.tsx: -------------------------------------------------------------------------------- 1 | import { DDClient } from '@datadog/ui-extensions-sdk'; 2 | import * as React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | type ControllerProps = { 6 | client: DDClient; 7 | }; 8 | 9 | /** 10 | * This component renders the main controller. 11 | * The main controller responds to the initial handshake from Datadog and sets up any App-wide behavior. 12 | * 13 | * @see https://github.com/DataDog/apps/blob/-/docs/en/programming-model.md#main-controller-iframe 14 | */ 15 | function Controller(props: ControllerProps): JSX.Element { 16 | return ( 17 | <> 18 |
The application controller is running in the background.
19 | 20 | Click here to open your custom widget 21 | 22 | 23 | ); 24 | } 25 | 26 | export function renderController(client: DDClient) { 27 | ReactDOM.render( 28 | 29 | 30 | , 31 | document.getElementById('root') 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | export * from './controller'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/custom-widget/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './custom-widget'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/index.ts: -------------------------------------------------------------------------------- 1 | import { client } from './client'; 2 | import { renderController } from './controller'; 3 | import { renderCustomWidget } from './custom-widget'; 4 | 5 | switch (window.location.pathname) { 6 | case '/custom-widget': 7 | renderCustomWidget(client); 8 | break; 9 | 10 | default: 11 | renderController(client); 12 | break; 13 | } 14 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-1st-party-data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "react-jsx", 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "es5" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/README.md: -------------------------------------------------------------------------------- 1 | # Datadog App 2 | 3 | ## Custom widget with 3rd-party data 4 | 5 | This App provides a custom widget that interacts with 3rd-party data. 6 | 3rd-party data is data provided by something external to Datadog (like your service or another service). 7 | It can be used as a starting point for building out a real-world App on the Datadog Developer Platform. 8 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/public/_redirects: -------------------------------------------------------------------------------- 1 | /custom-widget /index.html 200 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/custom-widget-with-3rd-party-data/public/favicon.ico -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/3rd-party/login/index.ts: -------------------------------------------------------------------------------- 1 | export { renderLogin } from './login'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/3rd-party/post/index.ts: -------------------------------------------------------------------------------- 1 | export { getPosts } from './post'; 2 | export type { Post } from './post'; 3 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/3rd-party/post/post.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The {@link Post} is the 3rd-party data we're integrating with. 3 | * The idea is that it represents any data that some 3rd-party provides that is being displayed in the custom widget. 4 | */ 5 | type Post = { 6 | title: string; 7 | }; 8 | 9 | /** 10 | * This function represent the idea of retrieving 3rd-party data. 11 | * It currently stubs out a couple of posts just to provide something. 12 | * 13 | * @param username The {@link User} to find {@link Post}s for. 14 | * @returns All of the {@link User}'s {@link Post}s. 15 | */ 16 | async function getPosts(username: string): Promise { 17 | /** 18 | * Simulate making a request to a 3rd-party that returns the {@link Post}s. 19 | */ 20 | return new Promise((resolve: (posts: Post[]) => void): void => { 21 | setTimeout((): void => { 22 | resolve([ 23 | { title: 'My first post' }, 24 | { title: 'The second post' }, 25 | { title: 'My last post' } 26 | ]); 27 | }, 100); 28 | }); 29 | } 30 | 31 | export { getPosts }; 32 | export type { Post }; 33 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/3rd-party/user/index.ts: -------------------------------------------------------------------------------- 1 | export { getUser, loginUser, logoutUser } from './user'; 2 | export type { User } from './user'; 3 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export { client } from './client'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/controller/controller.tsx: -------------------------------------------------------------------------------- 1 | import { DDClient } from '@datadog/ui-extensions-sdk'; 2 | import * as React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { useSetupCustomWidget } from './custom-widget'; 5 | 6 | type ControllerProps = { 7 | client: DDClient; 8 | }; 9 | 10 | /** 11 | * This component renders the main controller. 12 | * The main controller responds to the initial handshake from Datadog and sets up any App-wide behavior. 13 | * 14 | * @see https://github.com/DataDog/apps/blob/-/docs/en/programming-model.md#main-controller-iframe 15 | */ 16 | function Controller(props: ControllerProps): JSX.Element { 17 | useSetupCustomWidget(props.client); 18 | 19 | return ( 20 | <> 21 |
The application controller is running in the background.
22 | 23 | Click here to open your custom widget 24 | 25 | 26 | ); 27 | } 28 | 29 | function renderController(client: DDClient) { 30 | ReactDOM.render( 31 | 32 | 33 | , 34 | document.getElementById('root') 35 | ); 36 | } 37 | 38 | export { renderController }; 39 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | export { renderController } from './controller'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/custom-widget/index.ts: -------------------------------------------------------------------------------- 1 | export { renderCustomWidget } from './custom-widget'; 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/index.ts: -------------------------------------------------------------------------------- 1 | import { client } from './client'; 2 | import { renderController } from './controller'; 3 | import { renderCustomWidget } from './custom-widget'; 4 | import { renderLogin } from './3rd-party/login'; 5 | 6 | switch (window.location.pathname) { 7 | case '/custom-widget': 8 | renderCustomWidget(client); 9 | break; 10 | 11 | /** 12 | * This authentication route is part of this App, 13 | * but the system is designed to be able to leverage a preexisting authentication route if you have access to it. 14 | * If you already have an authentication route, 15 | * this route should be removed. 16 | */ 17 | case '/login': 18 | renderLogin(client); 19 | break; 20 | 21 | default: 22 | renderController(client); 23 | break; 24 | } 25 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/custom-widget-with-3rd-party-data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "react-jsx", 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "es5" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/geomap/.env.example: -------------------------------------------------------------------------------- 1 | # Datadog Keys 2 | DD_SITE=datadoghq.eu || datadoghq.com # Choose one 3 | DD_API_KEY= 4 | DD_APP_KEY= 5 | 6 | -------------------------------------------------------------------------------- /examples/geomap/.gitignore: -------------------------------------------------------------------------------- 1 | ## GLOBAL 2 | .env 3 | *.swp 4 | 5 | -------------------------------------------------------------------------------- /examples/geomap/crystal-api/.gitignore: -------------------------------------------------------------------------------- 1 | # Binary 2 | bin 3 | -------------------------------------------------------------------------------- /examples/geomap/crystal-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM crystallang/crystal:1.3.0-alpine-build as base 2 | ADD . /api 3 | WORKDIR /api 4 | RUN shards install 5 | EXPOSE 8080 6 | 7 | FROM base as build 8 | RUN shards build 9 | ENTRYPOINT ["bin/geomap"] 10 | HEALTHCHECK --start-period=15s --interval=15s --timeout=5s --retries=3 \ 11 | CMD curl -f http://localhost:8080 || exit 1 12 | 13 | -------------------------------------------------------------------------------- /examples/geomap/crystal-api/shard.lock: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | shards: {} 3 | -------------------------------------------------------------------------------- /examples/geomap/crystal-api/shard.yml: -------------------------------------------------------------------------------- 1 | name: geomap 2 | version: 1.0.0 3 | 4 | authors: 5 | - Thomas Dimnet 6 | - Matthieu Hermitte 7 | 8 | targets: 9 | geomap: 10 | main: src/geomap.cr 11 | 12 | crystal: 1.3.0 13 | 14 | license: MIT 15 | -------------------------------------------------------------------------------- /examples/geomap/crystal-api/src/geomap.cr: -------------------------------------------------------------------------------- 1 | ../main.cr -------------------------------------------------------------------------------- /examples/geomap/datadog-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /datadog-app 4 | WORKDIR /datadog-app 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/public/_redirects: -------------------------------------------------------------------------------- 1 | /widget /index.html 200 2 | /modal /index.html 200 3 | /panel /index.html 200 -------------------------------------------------------------------------------- /examples/geomap/datadog-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/geomap/datadog-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/geomap/datadog-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | 3 | export default function setup() { 4 | init(); 5 | 6 | const root = document.getElementById('root'); 7 | if (!root) { 8 | return; 9 | } 10 | root.innerHTML = ` 11 |
12 | The application controller is running in the background. 13 |
14 | Click here to open your widget 15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica, arial, sans-serif; 3 | margin: 2em; 4 | } 5 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { 3 | case '/geo-map-widget': { 4 | let widget = await import('./widget/GeoMap'); 5 | return widget.default(); 6 | } 7 | default: { 8 | let controller = await import('./controller'); 9 | return controller.default(); 10 | } 11 | } 12 | }; 13 | 14 | init(); 15 | export {}; 16 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/geomap/datadog-app/src/widget/GeoMap/widget.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/geomap/datadog-app/src/widget/GeoMap/widget.css -------------------------------------------------------------------------------- /examples/geomap/datadog-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/geomap/docker-compose-api.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | crystal-api: 4 | build: 5 | context: crystal-api 6 | target: build 7 | environment: 8 | BIND: 0.0.0.0 9 | PORT: 8080 10 | ports: 11 | - '8080:8080' 12 | -------------------------------------------------------------------------------- /examples/geomap/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | crystal-api: 4 | build: 5 | context: crystal-api 6 | target: base 7 | stdin_open: true 8 | tty: true 9 | environment: 10 | BIND: 0.0.0.0 11 | PORT: 8080 12 | DEBUG: '${DEBUG:-true}' 13 | ports: 14 | - '8080:8080' 15 | volumes: 16 | - ./crystal-api:/api 17 | datadog-app: 18 | build: datadog-app 19 | stdin_open: true 20 | tty: true 21 | environment: 22 | REACT_APP_API_URL: http://localhost:8080 23 | ports: 24 | - '3000:3000' 25 | volumes: 26 | - ./datadog-app:/datadog-app 27 | setup: 28 | stdin_open: true 29 | tty: true 30 | build: setup 31 | env_file: ./.env 32 | volumes: 33 | - ./setup:/setup 34 | -------------------------------------------------------------------------------- /examples/geomap/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | crystal-api: 4 | build: 5 | context: crystal-api 6 | target: build 7 | environment: 8 | BIND: 0.0.0.0 9 | PORT: 8080 10 | ports: 11 | - '8080:8080' 12 | datadog-app: 13 | build: datadog-app 14 | command: yarn start 15 | environment: 16 | REACT_APP_API_URL: http://localhost:8080 17 | ports: 18 | - '3000:3000' 19 | setup: 20 | build: setup 21 | command: yarn start 22 | env_file: ./.env 23 | -------------------------------------------------------------------------------- /examples/geomap/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/geomap/hero.png -------------------------------------------------------------------------------- /examples/geomap/request-loop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euo pipefail 4 | 5 | ## bail if missing deps 6 | type curl jq 1>&2 7 | 8 | _curl(){ 9 | ## test ${URL} 10 | ## bail if response != 200 11 | curl ${URL:-127.0.0.1:8080} ${OPTS:--Lksfo/dev/null} ${EXTRA_OPTS:-} -w ' 12 | { 13 | "http" : { 14 | "status_code" : %{http_code} , 15 | "duration" : %{time_total} , 16 | "url" : "%{url_effective}" 17 | } 18 | } 19 | ' | jq ${JQ_OPTS:--ce} "${FILTER:-select( .http.status_code == ${STATUS_CODE:-200} )}" 20 | } 21 | 22 | ## loop-test ${URL} 23 | while _curl ; do sleep ${DELAY:-0} ; done 24 | -------------------------------------------------------------------------------- /examples/geomap/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /setup 4 | WORKDIR /setup 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/geomap/setup/constants.js: -------------------------------------------------------------------------------- 1 | const DD_SITE = process.env.DD_SITE; 2 | const DD_API_KEY = process.env.DD_API_KEY; 3 | const DD_APP_KEY = process.env.DD_APP_KEY; 4 | 5 | const BASE_URL = 6 | DD_SITE === 'datadoghq.eu' 7 | ? 'https://api.datadoghq.eu' 8 | : 'https://api.datadoghq.com'; 9 | 10 | const APP_URL = 'http://localhost:3000'; 11 | 12 | module.exports = { 13 | APP_URL, 14 | BASE_URL, 15 | DD_SITE, 16 | DD_API_KEY, 17 | DD_APP_KEY 18 | }; 19 | -------------------------------------------------------------------------------- /examples/geomap/setup/index.js: -------------------------------------------------------------------------------- 1 | const { v1 } = require('@datadog/datadog-api-client'); 2 | 3 | const { BASE_URL, DD_SITE } = require('./constants'); 4 | const createApp = require('./createDatadogApp'); 5 | const createDashboard = require('./createDatadogDashboard'); 6 | 7 | async function main() { 8 | // eslint-disable-next-line no-console 9 | console.log('Configuring your account'); 10 | 11 | const configuration = v1.createConfiguration(); 12 | v1.setServerVariables(configuration, { 13 | site: DD_SITE 14 | }); 15 | 16 | // eslint-disable-next-line no-console 17 | console.log('Creating and configuring the Datadog App'); 18 | const appId = await createApp(); 19 | 20 | // eslint-disable-next-line no-console 21 | console.log('Creating the dashboard and adding the different widgets'); 22 | await createDashboard(configuration, appId); 23 | 24 | // eslint-disable-next-line no-console 25 | console.log(`You can now go to ${BASE_URL}`); 26 | } 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /examples/geomap/setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "exit 0", 4 | "prepare": "exit 0", 5 | "start": "node index.js", 6 | "test": "echo TODO Add test script" 7 | }, 8 | "private": true, 9 | "name": "datadog-app-example-geomap-setup", 10 | "version": "1.0.0", 11 | "main": "index.js", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@datadog/datadog-api-client": "^1.0.0-beta.6", 15 | "node-fetch": "2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/kitchen-sink/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/kitchen-sink/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/README.md: -------------------------------------------------------------------------------- 1 | # Datadog App 2 | 3 | ## Kitchen sink 4 | 5 | This App showcases all the features available. 6 | It also showcases interactions with both 1st-party Datadog data and 3rd-party data. 7 | It is mostly provided as a reference for what all is possible and not meant to be a realistic example. 8 | -------------------------------------------------------------------------------- /examples/kitchen-sink/public/_redirects: -------------------------------------------------------------------------------- 1 | /custom-widget /index.html 200 2 | /modal /index.html 200 3 | /side-panel /index.html 200 4 | -------------------------------------------------------------------------------- /examples/kitchen-sink/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/kitchen-sink/public/favicon.ico -------------------------------------------------------------------------------- /examples/kitchen-sink/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/1st-party/monitor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './monitor'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/1st-party/monitor/monitor.ts: -------------------------------------------------------------------------------- 1 | export type Monitor = { 2 | id: number; 3 | name: string; 4 | }; 5 | 6 | function isRecord(args: unknown): args is Record { 7 | return ( 8 | typeof args === 'object' && args != null && args.constructor === Object 9 | ); 10 | } 11 | 12 | function parseMonitor(monitor: unknown): Monitor | undefined { 13 | if (!isRecord(monitor)) { 14 | return; 15 | } 16 | if (typeof monitor.id !== 'number') { 17 | return; 18 | } 19 | if (typeof monitor.name !== 'string') { 20 | return; 21 | } 22 | 23 | return { 24 | id: monitor.id, 25 | name: monitor.name 26 | }; 27 | } 28 | 29 | /** 30 | * We do a bit of work to make sure that the API response we get back is the correct shape. 31 | * 32 | * @param response The API response from the SDK. 33 | * @returns The parsed {@link Monitor}. 34 | */ 35 | export function parseMonitors(response: unknown): Monitor[] { 36 | if (!Array.isArray(response)) { 37 | return []; 38 | } 39 | 40 | return response 41 | .map(parseMonitor) 42 | .filter((monitor?: Monitor): monitor is Monitor => { 43 | return monitor != null; 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/3rd-party/host/index.ts: -------------------------------------------------------------------------------- 1 | export { getHostInformation, parseHostInformation } from './host'; 2 | export type { HostInformation } from './host'; 3 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/3rd-party/login/index.ts: -------------------------------------------------------------------------------- 1 | export { renderLogin } from './login'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/3rd-party/post/index.ts: -------------------------------------------------------------------------------- 1 | export { getPosts } from './post'; 2 | export type { Post } from './post'; 3 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/3rd-party/post/post.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The {@link Post} is the 3rd-party data we're integrating with. 3 | * The idea is that it represents any data that some 3rd-party provides that is being displayed in the custom widget. 4 | */ 5 | type Post = { 6 | title: string; 7 | }; 8 | 9 | /** 10 | * This function represent the idea of retrieving 3rd-party data. 11 | * It currently stubs out a couple of posts just to provide something. 12 | * 13 | * @param username The {@link User} to find {@link Post}s for. 14 | * @returns All of the {@link User}'s {@link Post}s. 15 | */ 16 | async function getPosts(username: string): Promise { 17 | /** 18 | * Simulate making a request to a 3rd-party that returns the {@link Post}s. 19 | */ 20 | return new Promise((resolve: (posts: Post[]) => void): void => { 21 | setTimeout((): void => { 22 | resolve([ 23 | { title: 'My first post' }, 24 | { title: 'The second post' }, 25 | { title: 'My last post' } 26 | ]); 27 | }, 100); 28 | }); 29 | } 30 | 31 | export { getPosts }; 32 | export type { Post }; 33 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/3rd-party/user/index.ts: -------------------------------------------------------------------------------- 1 | export { getUser, loginUser, logoutUser } from './user'; 2 | export type { User } from './user'; 3 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export { client } from './client'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | export { renderController } from './controller'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/custom-widget/index.ts: -------------------------------------------------------------------------------- 1 | export { renderCustomWidget } from './custom-widget'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/index.ts: -------------------------------------------------------------------------------- 1 | import { client } from './client'; 2 | import { renderController } from './controller'; 3 | import { renderCustomWidget } from './custom-widget'; 4 | import { renderLogin } from './3rd-party/login'; 5 | import { renderModal } from './modal'; 6 | import { renderSidePanel } from './side-panel'; 7 | 8 | switch (window.location.pathname) { 9 | case '/custom-widget': 10 | renderCustomWidget(client); 11 | break; 12 | 13 | /** 14 | * This authentication route is part of this App, 15 | * but the system is designed to be able to leverage a preexisting authentication route if you have access to it. 16 | * If you already have an authentication route, 17 | * this route should be removed. 18 | */ 19 | case '/login': 20 | renderLogin(client); 21 | break; 22 | 23 | case '/modal': 24 | renderModal(client); 25 | break; 26 | 27 | case '/side-panel': 28 | renderSidePanel(client); 29 | break; 30 | 31 | default: 32 | renderController(client); 33 | break; 34 | } 35 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/modal/index.ts: -------------------------------------------------------------------------------- 1 | export { renderModal } from './modal'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/src/side-panel/index.ts: -------------------------------------------------------------------------------- 1 | export { renderSidePanel } from './side-panel'; 2 | -------------------------------------------------------------------------------- /examples/kitchen-sink/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "react-jsx", 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "es5" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/random-dog/.env.example: -------------------------------------------------------------------------------- 1 | # The Dog API Key - https://thedogapi.com/ 2 | API_KEY= 3 | 4 | 5 | # Datadog Keys 6 | DD_SITE=datadoghq.eu || datadoghq.com # Choose one 7 | DD_API_KEY= 8 | DD_APP_KEY= 9 | 10 | -------------------------------------------------------------------------------- /examples/random-dog/.gitignore: -------------------------------------------------------------------------------- 1 | # Global 2 | .env 3 | *.swp 4 | 5 | -------------------------------------------------------------------------------- /examples/random-dog/README.md: -------------------------------------------------------------------------------- 1 | # Random Dog Image App 2 | 3 | ![A Dog Image in your Datadog Dashboard](hero.png) 4 | 5 | ## Prerequesites 6 | 7 | - Docker 8 | - A Datadog Account with: 9 | - An API Key 10 | - An application Key 11 | - A Dog API Key: you can create an account for free at https://thedogapi.com 12 | 13 | ## Getting Started 14 | 15 | Clone the repo 16 | 17 | ``` 18 | $ git clone git@github.com:DataDog/apps.git 19 | ``` 20 | 21 | Change to Random Dog Directory 22 | 23 | ``` 24 | $ cd ./cd examples/random-dog/ 25 | ``` 26 | 27 | Copy the example env file and add yours 28 | 29 | ``` 30 | $ cp .env.example .env 31 | ``` 32 | 33 | Build the Docker images 34 | 35 | ``` 36 | $ docker-compose build 37 | ``` 38 | 39 | Launch the Docker containers and go to Datadog. A new Dashboard has been 40 | created for you. Search for the Random Dog Image Dashboard. 41 | 42 | ``` 43 | $ docker-compose up 44 | ``` 45 | 46 | ## Contributing 47 | 48 | If you want to contribute to the project, don't hesitate to contact me at 49 | thomas.dimnet@datadoghq.com. 50 | 51 | There is also a docker-compose file for the dev env. 52 | Build your Docker images 53 | 54 | ``` 55 | $ docker-compose -f docker-compose-dev.yml build 56 | ``` 57 | 58 | Launch your Docker containers 59 | 60 | ``` 61 | $ docker-compose -f docker-compose-dev.yml up 62 | ``` 63 | 64 | Then, you can bash into them 65 | 66 | ``` 67 | $ docker container exec -ti ${containerId} bash 68 | ``` 69 | -------------------------------------------------------------------------------- /examples/random-dog/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | dog-image-widget: 4 | stdin_open: true 5 | tty: true 6 | image: dog-image-widget 7 | build: dog-image-widget 8 | container_name: dog-image-widget 9 | ports: 10 | - '3000:3000' 11 | volumes: 12 | - ./dog-image-widget:/app 13 | dog-api: 14 | stdin_open: true 15 | tty: true 16 | image: dog-api 17 | build: 18 | dockerfile: Dockerfile 19 | context: ./dog-api 20 | container_name: dog-api 21 | env_file: ./.env 22 | environment: 23 | - PORT=3001 24 | ports: 25 | - '3001:3001' 26 | volumes: 27 | - ./dog-api:/server 28 | dog-setup: 29 | stdin_open: true 30 | tty: true 31 | image: setup 32 | build: 33 | dockerfile: Dockerfile 34 | context: ./setup 35 | container_name: dog-setup 36 | env_file: ./.env 37 | volumes: 38 | - ./setup:/setup 39 | -------------------------------------------------------------------------------- /examples/random-dog/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | dog-image-widget: 4 | image: dog-image-widget 5 | build: dog-image-widget 6 | container_name: dog-image-widget 7 | command: yarn start 8 | ports: 9 | - '3000:3000' 10 | dog-api: 11 | image: dog-api 12 | build: dog-api 13 | container_name: dog-api 14 | command: yarn start 15 | env_file: ./.env 16 | environment: 17 | - PORT=3001 18 | ports: 19 | - '3001:3001' 20 | dog-setup: 21 | image: setup 22 | build: setup 23 | container_name: dog-setup 24 | command: yarn start 25 | env_file: ./.env 26 | -------------------------------------------------------------------------------- /examples/random-dog/dog-api/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /examples/random-dog/dog-api/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/random-dog/dog-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /server 4 | WORKDIR /server 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/random-dog/dog-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-random-dog-dog-api", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "exit 0", 9 | "dev": "node_modules/.bin/nodemon index.js", 10 | "prepare": "exit 0", 11 | "start": "node index.js", 12 | "test": "echo TODO Add test script" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "axios": "^0.24.0", 19 | "cors": "^2.8.5", 20 | "express": "^4.17.1" 21 | }, 22 | "devDependencies": { 23 | "nodemon": "^2.0.15" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /app 4 | WORKDIR /app 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/README.md: -------------------------------------------------------------------------------- 1 | # Random Dog Image Widget for Datadog 2 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/app_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "v2", 3 | "display_on_public_website": false, 4 | "app_id": null, 5 | "support_type": "partner", 6 | "tile": { 7 | "description": "App to display random do images in a custom widget", 8 | "logo_media": { 9 | "light": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Emojione_1F44B.svg/1024px-Emojione_1F44B.svg.png" 10 | }, 11 | "title": "Random Dog Images" 12 | }, 13 | "author": {}, 14 | "pricing": [], 15 | "legal_terms": {}, 16 | "proxy_scopes": [], 17 | "assets": { 18 | "ui_extensions": { 19 | "debug_mode_url": "http://localhost:3000", 20 | "secured": false, 21 | "main_url": "http://localhost:3000", 22 | "api_version": "v1.0", 23 | "features": [ 24 | { 25 | "name": "dashboard_custom_widget", 26 | "options": { 27 | "widgets": [ 28 | { 29 | "name": "Random Dog Images", 30 | "has_title": true, 31 | "custom_widget_key": "random-dog-images", 32 | "source": "widget" 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-random-dog-dog-image-widget", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@datadog/ui-extensions-react": "0.32.2", 7 | "@datadog/ui-extensions-sdk": "0.32.2", 8 | "@types/node": "^14.14.14", 9 | "@types/react": "^17.0.0", 10 | "@types/react-dom": "^17.0.0", 11 | "milligram": "^1.4.1", 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0", 14 | "react-scripts": "5.0.1", 15 | "typeface-roboto": "^1.1.13", 16 | "typescript": "^4.1.3" 17 | }, 18 | "scripts": { 19 | "build": "react-scripts build", 20 | "eject": "react-scripts eject", 21 | "prepare": "exit 0", 22 | "start": "react-scripts start", 23 | "test": "react-scripts test --passWithNoTests --watchAll=false" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/public/_redirects: -------------------------------------------------------------------------------- 1 | /widget /index.html 200 2 | /modal /index.html 200 3 | /panel /index.html 200 -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/random-dog/dog-image-widget/public/favicon.ico -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | 3 | export default function setup() { 4 | init(); 5 | 6 | const root = document.getElementById('root'); 7 | if (!root) { 8 | return; 9 | } 10 | root.innerHTML = ` 11 |
12 | The application controller is running in the background. 13 |
14 | Click here to open your widget 15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica, arial, sans-serif; 3 | } 4 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/src/index.tsx: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { 3 | case '/widget': { 4 | let widget = await import('./widget'); 5 | return widget.default(); 6 | } 7 | default: { 8 | let controller = await import('./controller'); 9 | return controller.default(); 10 | } 11 | } 12 | }; 13 | 14 | init(); 15 | export {}; 16 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/src/widget/widget.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | } 4 | 5 | .main-wrapper { 6 | overflow: hidden; 7 | position: relative; 8 | } 9 | 10 | .new-dog-btn, 11 | .new-dog-btn:focus { 12 | background-color: #642ca5; 13 | border: none; 14 | color: white; 15 | left: 20px; 16 | position: absolute; 17 | top: 20px; 18 | transition: 0.4s; 19 | } 20 | 21 | .new-dog-btn:hover { 22 | background-color: #9743f7; 23 | } 24 | 25 | .dog-img { 26 | object-fit: contain; 27 | width: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /examples/random-dog/dog-image-widget/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/random-dog/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/random-dog/hero.png -------------------------------------------------------------------------------- /examples/random-dog/setup/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/random-dog/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /setup 4 | WORKDIR /setup 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/random-dog/setup/constants.js: -------------------------------------------------------------------------------- 1 | const DD_SITE = process.env.DD_SITE; 2 | const DD_API_KEY = process.env.DD_API_KEY; 3 | const DD_APP_KEY = process.env.DD_APP_KEY; 4 | 5 | const BASE_URL = 6 | DD_SITE === 'datadoghq.eu' 7 | ? 'https://api.datadoghq.eu' 8 | : 'https://api.datadoghq.com'; 9 | 10 | const APP_URL = 'http://localhost:3000'; 11 | 12 | module.exports = { 13 | APP_URL, 14 | BASE_URL, 15 | DD_SITE, 16 | DD_API_KEY, 17 | DD_APP_KEY 18 | }; 19 | -------------------------------------------------------------------------------- /examples/random-dog/setup/index.js: -------------------------------------------------------------------------------- 1 | const { v1 } = require('@datadog/datadog-api-client'); 2 | 3 | const { DD_SITE } = require('./constants'); 4 | const createApp = require('./createDatadogApp'); 5 | const createDashboard = require('./createDatadogDashboard'); 6 | 7 | const main = async () => { 8 | // eslint-disable-next-line no-console 9 | console.log('Configuring your account'); 10 | const configuration = v1.createConfiguration(); 11 | v1.setServerVariables(configuration, { 12 | site: DD_SITE 13 | }); 14 | 15 | // eslint-disable-next-line no-console 16 | console.log('Creating the app'); 17 | const id = await createApp(configuration); 18 | 19 | // eslint-disable-next-line no-console 20 | console.log('Creating the dashboard and attaching the app'); 21 | await createDashboard(configuration, id); 22 | }; 23 | 24 | main(); 25 | -------------------------------------------------------------------------------- /examples/random-dog/setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "exit 0", 5 | "prepare": "exit 0", 6 | "start": "node index.js", 7 | "test": "echo TODO Add test script" 8 | }, 9 | "name": "datadog-app-example-random-dog-setup", 10 | "version": "1.0.0", 11 | "main": "index.js", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@datadog/datadog-api-client": "^1.0.0-beta.6", 15 | "node-fetch": "^2.6.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/redis/.env.example: -------------------------------------------------------------------------------- 1 | # Datadog Keys 2 | DD_SITE=datadoghq.eu || datadoghq.com # Choose one 3 | DD_API_KEY= 4 | DD_APP_KEY= 5 | 6 | -------------------------------------------------------------------------------- /examples/redis/.gitignore: -------------------------------------------------------------------------------- 1 | ## GLOBAL 2 | .env 3 | *.swp 4 | 5 | -------------------------------------------------------------------------------- /examples/redis/README.md: -------------------------------------------------------------------------------- 1 | # Datadog Redis App 2 | 3 | ![The Datadog Dashboard and the Datadog App of the project](redis-app-hero.png) 4 | 5 | ## Prerequesites 6 | 7 | - Docker 8 | - A Datadog Account with: 9 | - An API Key 10 | - An application Key 11 | 12 | ## Getting Started 13 | 14 | Clone the repo 15 | 16 | ``` 17 | $ git clone git@github.com:DataDog/apps.git 18 | ``` 19 | 20 | Change to Random Dog Directory 21 | 22 | ``` 23 | $ cd ./cd examples/redis/ 24 | ``` 25 | 26 | Copy the example env file and add yours 27 | 28 | ``` 29 | $ cp .env.example .env 30 | ``` 31 | 32 | Build the Docker images 33 | 34 | ``` 35 | $ docker-compose build 36 | ``` 37 | 38 | Launch the Docker containers and go to Datadog. 39 | A Dashboard and an Datadog App has been created for you. 40 | 41 | Search for the Datadog App Redis Dashboard. 42 | 43 | ``` 44 | $ docker-compose up 45 | ``` 46 | 47 | ## Contributing 48 | 49 | If you want to contribute to the project, don't hesitate to contact me at 50 | thomas.dimnet@datadoghq.com. 51 | 52 | There is also a docker-compose file for the dev env. 53 | Build your Docker images 54 | 55 | ``` 56 | $ docker-compose -f docker-compose-dev.yml build 57 | ``` 58 | 59 | Launch your Docker containers 60 | 61 | ``` 62 | $ docker-compose -f docker-compose-dev.yml up 63 | ``` 64 | 65 | Then, you can bash into them 66 | 67 | ``` 68 | $ docker container exec -ti ${containerId} bash 69 | ``` 70 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /datadog-app 4 | WORKDIR /datadog-app 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-redis-datadog-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@datadog/ui-extensions-react": "0.32.2", 7 | "@datadog/ui-extensions-sdk": "0.32.2", 8 | "@testing-library/jest-dom": "^5.16.3", 9 | "@testing-library/react": "^13.0.0", 10 | "@testing-library/user-event": "^14.0.4", 11 | "react": "^18.0.0", 12 | "react-dom": "^18.0.0", 13 | "react-hook-form": "^7.19.5", 14 | "react-scripts": "5.0.1", 15 | "web-vitals": "^1.0.1" 16 | }, 17 | "scripts": { 18 | "build": "react-scripts build", 19 | "eject": "react-scripts eject", 20 | "prepare": "exit 0", 21 | "start": "PORT=3010 react-scripts start", 22 | "test": "react-scripts test --watchAll=false" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/redis/datadog-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/redis/datadog-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/redis/datadog-app/public/logo192.png -------------------------------------------------------------------------------- /examples/redis/datadog-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/redis/datadog-app/public/logo512.png -------------------------------------------------------------------------------- /examples/redis/datadog-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from './logo.svg'; 2 | import './App.css'; 3 | 4 | function App() { 5 | return ( 6 |
7 |
8 | logo 9 |

10 | Edit src/App.js and save to reload. 11 |

12 | 18 | Learn React 19 | 20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/controller/index.js: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | 3 | export default function setup() { 4 | init(); 5 | 6 | const root = document.getElementById('root'); 7 | 8 | if (!root) return; 9 | 10 | root.innerHTML = ` 11 |
12 |

Datadog Redis App

13 |

Browse Datadoghq.com ou Datadoghq.eu to find out more

14 |
15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 4 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 5 | 'Helvetica Neue', sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/index.js: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { 3 | case '/clients-list': { 4 | const widget = await import('./widgets/ClientsList'); 5 | return widget.default(); 6 | } 7 | case '/keys-search-widget': { 8 | const widget = await import('./widgets/KeySearch'); 9 | return widget.default(); 10 | } 11 | case '/keys-search-modal': { 12 | const modal = await import('./modals/KeySearch'); 13 | return modal.default(); 14 | } 15 | default: { 16 | const controller = await import('./controller'); 17 | return controller.default(); 18 | } 19 | } 20 | }; 21 | 22 | init(); 23 | export {}; 24 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/modals/KeySearch/index.css: -------------------------------------------------------------------------------- 1 | .sk-form { 2 | margin-bottom: 16px; 3 | } 4 | 5 | .sk-key { 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | .sk-key button { 11 | margin-top: 16px; 12 | } 13 | 14 | .sk-btn-group { 15 | margin-left: 16px; 16 | margin-bottom: 16px; 17 | } 18 | 19 | .sk-modify-btn { 20 | margin-right: 16px; 21 | } 22 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then( 4 | ({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 5 | getCLS(onPerfEntry); 6 | getFID(onPerfEntry); 7 | getFCP(onPerfEntry); 8 | getLCP(onPerfEntry); 9 | getTTFB(onPerfEntry); 10 | } 11 | ); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/sidePanels/KeyInfo/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/redis/datadog-app/src/sidePanels/KeyInfo/index.css -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/sidePanels/KeyInfo/index.js: -------------------------------------------------------------------------------- 1 | import { useContext } from '@datadog/ui-extensions-react'; 2 | import { init } from '@datadog/ui-extensions-sdk'; 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import './index.css'; 7 | 8 | const client = init(); 9 | 10 | const SidePanel = () => { 11 | const context = useContext(client); 12 | 13 | if (context === undefined) return
Loading...
; 14 | 15 | const redisMetric = context.args.redisData; 16 | 17 | return ( 18 |
19 | Redis Data: 20 |
    21 |
  • Key: {redisMetric.key}
  • 22 |
  • Value: {redisMetric.value}
  • 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default function render() { 29 | ReactDOM.render( 30 | 31 | 32 | , 33 | document.getElementById('root') 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/widgets/ClientsList/index.css: -------------------------------------------------------------------------------- 1 | .client { 2 | display: flex; 3 | justify-content: space-between; 4 | margin-bottom: 16px; 5 | } 6 | 7 | .client-name { 8 | font-weight: bold; 9 | } 10 | -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/widgets/KeySearch/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/redis/datadog-app/src/widgets/KeySearch/index.css -------------------------------------------------------------------------------- /examples/redis/datadog-app/src/widgets/KeySearch/index.js: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | import './index.css'; 6 | 7 | const client = init(); 8 | 9 | const Widget = () => { 10 | const onOpenModal = () => { 11 | client.modal.open({ 12 | key: 'search-key-modal', 13 | source: 'keys-search-modal', 14 | size: 'md' 15 | }); 16 | }; 17 | 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | 25 | export default function render() { 26 | ReactDOM.render( 27 | 28 | 29 | , 30 | document.getElementById('root') 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/redis/datadog/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/datadoghq/agent:latest 2 | ADD conf.d/redisdb.yaml /etc/datadog-agent/conf.d/redisdb.yaml 3 | -------------------------------------------------------------------------------- /examples/redis/datadog/conf.d/redisdb.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - host: redis 5 | port: 6379 6 | -------------------------------------------------------------------------------- /examples/redis/flask-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | ADD . /flask-app 4 | WORKDIR /flask-app 5 | 6 | RUN pip install -r requirements.txt 7 | 8 | -------------------------------------------------------------------------------- /examples/redis/flask-app/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==21.2.0 2 | click==8.0.3 3 | ddtrace==0.55.4 4 | Flask==2.0.2 5 | Flask-Cors==3.0.10 6 | itsdangerous==2.0.1 7 | Jinja2==3.0.2 8 | MarkupSafe==2.0.1 9 | packaging==21.2 10 | protobuf==3.19.1 11 | pyparsing==2.4.7 12 | redis==3.5.3 13 | six==1.16.0 14 | tenacity==8.0.1 15 | Werkzeug==2.0.2 16 | -------------------------------------------------------------------------------- /examples/redis/products-api/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/redis/products-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /products-api 4 | WORKDIR /products-api 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/redis/products-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-redis-products-api", 3 | "private": true, 4 | "scripts": { 5 | "build": "exit 0", 6 | "dev": "nodemon ./src/app.js", 7 | "prepare": "exit 0", 8 | "seed": "node ./src/db/seed.js", 9 | "serve": "node --require dd-trace/init src/app.js", 10 | "test": "echo TODO Add test script" 11 | }, 12 | "version": "0.0.0", 13 | "dependencies": { 14 | "body-parser": "^1.19.0", 15 | "dd-trace": "^1.5.1", 16 | "express": "^4.17.1", 17 | "faker": "^5.5.3", 18 | "mongoose": "8.2.4", 19 | "redis": "^4.0.0" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^2.0.15" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/redis/products-api/src/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const jsonParser = require('body-parser').json; 3 | 4 | const { createConnection, createRedisClient } = require('./db/index'); 5 | const products = require('./routes/products'); 6 | const users = require('./routes/users'); 7 | 8 | const app = express(); 9 | const port = process.env.PORT || 3020; 10 | 11 | app.use(jsonParser()); 12 | 13 | app.use('/api/v1/products', products); 14 | app.use('/api/v1/users', users); 15 | 16 | app.listen(port, async () => { 17 | await createConnection(); 18 | 19 | const redisClient = createRedisClient(); 20 | await redisClient.connect(); 21 | // eslint-disable-next-line no-console 22 | console.log('Redis: connection successful'); 23 | 24 | // eslint-disable-next-line no-console 25 | console.log(`Magic Happens on port ${port}`); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/redis/products-api/src/db/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { createClient } = require('redis'); 3 | 4 | const createConnection = async () => { 5 | try { 6 | await mongoose.connect(process.env.MONGO_URL, { 7 | user: process.env.MONGO_USERNAME, 8 | pass: process.env.MONGO_PASSWORD, 9 | dbName: process.env.MONGO_DB 10 | }); 11 | 12 | // eslint-disable-next-line no-console 13 | console.log('MongoDB: connection successful'); 14 | } catch (error) { 15 | // eslint-disable-next-line no-console 16 | console.log('An error occurs when connecting to the mongodb', error); 17 | } 18 | }; 19 | 20 | function createRedisClient() { 21 | const client = createClient({ 22 | socket: { 23 | host: process.env.REDIS_HOST, 24 | port: process.env.REDIS_PORT 25 | } 26 | }); 27 | 28 | // eslint-disable-next-line no-console 29 | client.on('error', error => console.log('Redis Client Error:', error)); 30 | 31 | return client; 32 | } 33 | 34 | module.exports = { 35 | createConnection, 36 | createRedisClient 37 | }; 38 | -------------------------------------------------------------------------------- /examples/redis/products-api/src/models/product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const ProductSchema = new mongoose.Schema({ 4 | name: String, 5 | description: String, 6 | price: Number 7 | }); 8 | 9 | const Product = mongoose.model('Product', ProductSchema); 10 | 11 | module.exports = Product; 12 | -------------------------------------------------------------------------------- /examples/redis/products-api/src/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const UserSchema = new mongoose.Schema({ 4 | username: String, 5 | emailAddress: String, 6 | password: String 7 | }); 8 | 9 | const User = mongoose.model('User', UserSchema); 10 | 11 | module.exports = User; 12 | -------------------------------------------------------------------------------- /examples/redis/products-api/src/routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const { createRedisClient } = require('../db/index'); 4 | 5 | const router = express.Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | res.send('Get request on users endpoint'); 9 | }); 10 | 11 | router.post('/', async (req, res, next) => { 12 | res.send('Post request on users endpoint'); 13 | }); 14 | 15 | router.get('/:userId', async (req, res, next) => { 16 | const { 17 | params: { userId } 18 | } = req; 19 | 20 | const redisClient = createRedisClient(); 21 | await redisClient.connect(); 22 | 23 | await redisClient.sendCommand(['CLIENT', 'SETNAME', 'products-api']); 24 | 25 | res.send(`Get request on userId ${userId}`); 26 | }); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /examples/redis/redis-app-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/redis/redis-app-hero.png -------------------------------------------------------------------------------- /examples/redis/setup/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/redis/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /setup 4 | WORKDIR /setup 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/redis/setup/constants.js: -------------------------------------------------------------------------------- 1 | const DD_SITE = process.env.DD_SITE; 2 | const DD_API_KEY = process.env.DD_API_KEY; 3 | const DD_APP_KEY = process.env.DD_APP_KEY; 4 | 5 | const BASE_URL = 6 | DD_SITE === 'datadoghq.eu' 7 | ? 'https://api.datadoghq.eu' 8 | : 'https://api.datadoghq.com'; 9 | 10 | const APP_URL = 'http://localhost:3010'; 11 | 12 | module.exports = { 13 | APP_URL, 14 | BASE_URL, 15 | DD_SITE, 16 | DD_API_KEY, 17 | DD_APP_KEY 18 | }; 19 | -------------------------------------------------------------------------------- /examples/redis/setup/index.js: -------------------------------------------------------------------------------- 1 | const { v1 } = require('@datadog/datadog-api-client'); 2 | 3 | const { DD_SITE } = require('./constants'); 4 | const createApp = require('./createDatadogApp'); 5 | const createDashboard = require('./createDatadogDashboard'); 6 | 7 | async function main() { 8 | // eslint-disable-next-line no-console 9 | console.log('Configuring your account'); 10 | 11 | const configuration = v1.createConfiguration(); 12 | v1.setServerVariables(configuration, { 13 | site: DD_SITE 14 | }); 15 | 16 | // eslint-disable-next-line no-console 17 | console.log('Creating and configuring the Datadog App'); 18 | const appId = await createApp(); 19 | 20 | // eslint-disable-next-line no-console 21 | console.log('Creating the dashboard and adding the different widgets'); 22 | await createDashboard(configuration, appId); 23 | } 24 | 25 | main(); 26 | -------------------------------------------------------------------------------- /examples/redis/setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "exit 0", 5 | "prepare": "exit 0", 6 | "start": "node index.js", 7 | "test": "echo TODO Add test script" 8 | }, 9 | "name": "datadog-app-example-redis-setup", 10 | "version": "1.0.0", 11 | "main": "index.js", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@datadog/datadog-api-client": "^1.0.0-beta.6", 15 | "node-fetch": "^2.6.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/sentiment/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .package-lock.json 25 | 26 | .eslintcache -------------------------------------------------------------------------------- /examples/sentiment/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | -------------------------------------------------------------------------------- /examples/sentiment/docs/main-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/main-image.png -------------------------------------------------------------------------------- /examples/sentiment/docs/sentiment-video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/sentiment-video.mp4 -------------------------------------------------------------------------------- /examples/sentiment/docs/step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-1.png -------------------------------------------------------------------------------- /examples/sentiment/docs/step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-2.png -------------------------------------------------------------------------------- /examples/sentiment/docs/step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-3.png -------------------------------------------------------------------------------- /examples/sentiment/docs/step-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-4.png -------------------------------------------------------------------------------- /examples/sentiment/docs/step-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-5.png -------------------------------------------------------------------------------- /examples/sentiment/docs/step-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-6.png -------------------------------------------------------------------------------- /examples/sentiment/docs/step-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/docs/step-7.png -------------------------------------------------------------------------------- /examples/sentiment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-sentiment", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@datadog/ui-extensions-react": "0.32.2", 7 | "@datadog/ui-extensions-sdk": "0.32.2", 8 | "milligram": "^1.4.1", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-scripts": "5.0.1", 12 | "typeface-roboto": "^1.1.13" 13 | }, 14 | "scripts": { 15 | "build": "react-scripts build", 16 | "eject": "react-scripts eject", 17 | "prepare": "exit 0", 18 | "start": "react-scripts start", 19 | "test": "react-scripts test --passWithNoTests --watchAll=false" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/sentiment/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/public/favicon.ico -------------------------------------------------------------------------------- /examples/sentiment/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/public/logo192.png -------------------------------------------------------------------------------- /examples/sentiment/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/sentiment/public/logo512.png -------------------------------------------------------------------------------- /examples/sentiment/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/sentiment/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/sentiment/src/controller/dashboard-cog-menu.js: -------------------------------------------------------------------------------- 1 | import { EventType, ModalSize, MenuItemType } from '@datadog/ui-extensions-sdk'; 2 | 3 | export const setupDashboardCogMenu = client => { 4 | // provide cog menu items dynamically if needed 5 | 6 | client.dashboard.cogMenu.onRequest(() => { 7 | return { 8 | // Setting up a custom cog menu URL. 9 | items: [ 10 | { 11 | actionType: MenuItemType.LINK, 12 | href: 'https://developer.twitter.com/', 13 | label: 'Twitter Developer Portal', 14 | key: 'link', 15 | order: 2 16 | } 17 | ] 18 | }; 19 | }); 20 | 21 | // listen for cog menu click events 22 | client.events.on(EventType.DASHBOARD_COG_MENU_CLICK, context => { 23 | // open an iframe modal defined inline here in controller 24 | if (context.menuItem.key === 'open-custom-modal') { 25 | client.modal.open({ 26 | key: 'custom-modal', 27 | size: ModalSize.LARGE, 28 | source: 'modal' 29 | }); 30 | } 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/sentiment/src/controller/index.js: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | import { setupModal } from './modal'; 3 | import { setupDashboardCogMenu } from './dashboard-cog-menu'; 4 | 5 | export default function setup() { 6 | const client = init(); 7 | 8 | setupModal(client); 9 | setupDashboardCogMenu(client); 10 | 11 | const root = document.getElementById('root'); 12 | if (!root) { 13 | return; 14 | } 15 | root.innerHTML = ` 16 |
17 | The application controller is running in the background. 18 |
19 | Click here to open your widget 20 | `; 21 | } 22 | -------------------------------------------------------------------------------- /examples/sentiment/src/controller/modal.js: -------------------------------------------------------------------------------- 1 | import { EventType } from '@datadog/ui-extensions-sdk'; 2 | 3 | export const setupModal = client => { 4 | // listen for modal events 5 | client.events.on(EventType.MODAL_ACTION, () => { 6 | console.log('Confirmed!'); 7 | }); 8 | 9 | client.events.on(EventType.MODAL_CANCEL, () => { 10 | console.log('Denied!'); 11 | }); 12 | 13 | client.events.on(EventType.MODAL_CLOSE, definition => { 14 | console.log(`User exited modal ${definition.key}`); 15 | }); 16 | 17 | // listen for a custom event sent from modal IFrame 18 | client.events.onCustom('modal_button_click', number => { 19 | console.log(`The user has clicked the button ${number} times`); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/sentiment/src/index.js: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { 3 | case '/widget': { 4 | let widget = await import('./widget'); 5 | return widget.default(); 6 | } 7 | case '/panel': { 8 | let sidepanel = await import('./side-panel'); 9 | return sidepanel.default(); 10 | } 11 | case '/modal': { 12 | let modal = await import('./modal'); 13 | return modal.default(); 14 | } 15 | default: { 16 | let controller = await import('./controller'); 17 | console.log('default is getting called'); 18 | return controller.default(); 19 | } 20 | } 21 | }; 22 | 23 | init(); 24 | export {}; 25 | -------------------------------------------------------------------------------- /examples/sentiment/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then( 4 | ({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 5 | getCLS(onPerfEntry); 6 | getFID(onPerfEntry); 7 | getFCP(onPerfEntry); 8 | getLCP(onPerfEntry); 9 | getTTFB(onPerfEntry); 10 | } 11 | ); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /examples/sentiment/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /examples/sentiment/src/side-panel/index.js: -------------------------------------------------------------------------------- 1 | import { useContext } from '@datadog/ui-extensions-react'; 2 | import { init } from '@datadog/ui-extensions-sdk'; 3 | import './../index.css'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import 'milligram'; 7 | 8 | import Sentiment from './Sentiment'; 9 | 10 | const client = init(); 11 | 12 | function SidePanel() { 13 | const context = useContext(client); 14 | const sentimentTweet = context?.args.args; 15 | 16 | return ( 17 |
25 | {sentimentTweet ? ( 26 | 27 | ) : null} 28 |
29 | ); 30 | } 31 | 32 | export default function render() { 33 | ReactDOM.render( 34 | {}, 35 | document.getElementById('root') 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /examples/sentiment/src/widget/Tweets.js: -------------------------------------------------------------------------------- 1 | const Tweets = tweets => { 2 | let clickable = tweets.onClick, 3 | altText = 'twitter avatar of ' + tweets.tweets.name; 4 | 5 | return ( 6 |
7 |
13 |
14 | {altText} 19 |
20 |
21 | {tweets.tweets.name}{' '} 22 | @{tweets.tweets.username} 23 |

{tweets.tweets.text}

24 |
25 |
26 |
27 |

28 | Sentiment: {tweets.tweets.sentiment} 29 |

30 |
31 |
32 | ); 33 | }; 34 | 35 | export default Tweets; 36 | -------------------------------------------------------------------------------- /examples/sentiment/src/widget/widget.css: -------------------------------------------------------------------------------- 1 | /* CSS files add styling rules to your content */ 2 | 3 | h1 { 4 | font-style: italic; 5 | color: #373fff; 6 | } 7 | -------------------------------------------------------------------------------- /examples/slice-pizza/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME="Slice Pizza" 2 | 3 | # Datadog Keys 4 | DD_API_KEY= 5 | DD_APP_KEY= 6 | 7 | # datadoghq.eu || datadoghq.com 8 | DD_SITE= 9 | -------------------------------------------------------------------------------- /examples/slice-pizza/.gitignore: -------------------------------------------------------------------------------- 1 | # Global stuff 2 | .env 3 | *.swp 4 | 5 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.14-buster-slim 2 | 3 | ADD . /datadog-app 4 | WORKDIR /datadog-app 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/_redirects: -------------------------------------------------------------------------------- 1 | /widget /index.html 200 2 | /modal /index.html 200 3 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/header.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/logo.jpeg -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/logo.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza-item.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza-item.jpg -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza__diavolo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza__diavolo.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza__harissa_lamb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza__harissa_lamb.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza__mushrooms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza__mushrooms.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza__sausage_peppers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza__sausage_peppers.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza__standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza__standard.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/img/pizza__tomato.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/public/img/pizza__tomato.png -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/components/OrderSuccess.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Container, 3 | Text, 4 | Image, 5 | AspectRatio, 6 | Stack, 7 | Heading 8 | } from '@chakra-ui/react'; 9 | 10 | import Token from '../types/Token'; 11 | 12 | export function OrderSuccess(props: { token: Token }) { 13 | return ( 14 | 15 | 16 | 17 | 18 | Thank you for your order! 19 | 20 | {props?.token?.email && ( 21 | 22 | We've sent an order confirmation email to{' '} 23 | {props.token.email}. 24 | 25 | )} 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/config.js: -------------------------------------------------------------------------------- 1 | export const PROXY_URL = process.env.REACT_APP_PROXY_URL; 2 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | 3 | export default function setup() { 4 | // eslint-disable-next-line 5 | const client = init(); 6 | 7 | const root = document.getElementById('root'); 8 | if (!root) { 9 | return; 10 | } 11 | root.innerHTML = ` 12 |
13 | The application controller is running in the background. 14 |
15 | Click here to open your widget 16 | `; 17 | } 18 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/img/pizza-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/src/img/pizza-bg.jpg -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/img/pizza-maker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/datadog-app/src/img/pizza-maker.jpg -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { 3 | case '/slice-pizza-widget': { 4 | let widget = await import('./widget/SlicePizza'); 5 | return widget.default(); 6 | } 7 | case '/slice-pizza-modal': { 8 | let modal = await import('./modal/SlicePizza'); 9 | return modal.default(); 10 | } 11 | default: { 12 | let controller = await import('./controller'); 13 | return controller.default(); 14 | } 15 | } 16 | }; 17 | 18 | init(); 19 | export {}; 20 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/types/Order.ts: -------------------------------------------------------------------------------- 1 | import Pizza from './Pizza'; 2 | 3 | export default interface Order { 4 | items: Pizza[]; 5 | total: number; 6 | } 7 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/types/Pizza.ts: -------------------------------------------------------------------------------- 1 | export default interface Pizza { 2 | description: string; 3 | id: string; 4 | image: string; 5 | name: string; 6 | price: number; 7 | quantity: number; 8 | amount: number; 9 | } 10 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/types/Token.ts: -------------------------------------------------------------------------------- 1 | export default interface Token { 2 | email: string; 3 | id: string; 4 | expires: number; 5 | } 6 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/types/User.ts: -------------------------------------------------------------------------------- 1 | export default interface User { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/utils/currency.ts: -------------------------------------------------------------------------------- 1 | export default new Intl.NumberFormat('en-US', { 2 | style: 'currency', 3 | currency: 'USD' 4 | }); 5 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/src/widget/SlicePizza/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { init, ModalSize } from '@datadog/ui-extensions-sdk'; 4 | import { 5 | ChakraProvider, 6 | Container, 7 | Button, 8 | Image, 9 | Stack 10 | } from '@chakra-ui/react'; 11 | 12 | import '@fontsource/tourney/variable.css'; 13 | import '@fontsource/bebas-neue/index.css'; 14 | 15 | import theme from '../../theme'; 16 | 17 | // eslint-disable-next-line 18 | const client = init(); 19 | 20 | function Widget() { 21 | const onOpenModal = () => { 22 | client.modal.open({ 23 | key: 'slice-pizza-modal', 24 | source: 'slice-pizza-modal', 25 | size: ModalSize.LARGE 26 | }); 27 | }; 28 | 29 | return ( 30 | 31 | 32 | 33 | 36 | 37 | 38 | ); 39 | } 40 | 41 | export default function render() { 42 | ReactDOM.render( 43 | 44 | 45 | 46 | 47 | , 48 | document.getElementById('root') 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /examples/slice-pizza/datadog-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/slice-pizza/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: datadog-app 5 | stdin_open: true 6 | tty: true 7 | environment: 8 | REACT_APP_API_URL: http://localhost:3001 9 | REACT_APP_PROXY_URL: http://localhost:5000 10 | ports: 11 | - '3002:3000' 12 | volumes: 13 | - ./datadog-app:/datadog-app 14 | 15 | api: 16 | build: slice-life-pizzeria 17 | stdin_open: true 18 | tty: true 19 | ports: 20 | - '3001:3000' 21 | volumes: 22 | - ./slice-life-pizzeria:/api 23 | 24 | proxy: 25 | build: proxy 26 | environment: 27 | API_URL: http://api:3000/api 28 | stdin_open: true 29 | tty: true 30 | ports: 31 | - '5000:5000' 32 | volumes: 33 | - ./proxy:/proxy 34 | 35 | setup: 36 | stdin_open: true 37 | tty: true 38 | build: setup 39 | env_file: 40 | - ./.env 41 | volumes: 42 | - ./setup:/setup 43 | -------------------------------------------------------------------------------- /examples/slice-pizza/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | command: yarn start 5 | build: datadog-app 6 | environment: 7 | REACT_APP_API_URL: http://localhost:3001 8 | REACT_APP_PROXY_URL: http://localhost:5000 9 | ports: 10 | - '3002:3000' 11 | 12 | api: 13 | command: node index.js 14 | build: slice-life-pizzeria 15 | ports: 16 | - '3001:3000' 17 | 18 | proxy: 19 | build: proxy 20 | command: flask run --port=5000 --host=0.0.0.0 21 | environment: 22 | API_URL: http://api:3000/api 23 | ports: 24 | - '5000:5000' 25 | 26 | setup: 27 | command: yarn start 28 | build: setup 29 | env_file: 30 | - ./.env 31 | -------------------------------------------------------------------------------- /examples/slice-pizza/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/hero.png -------------------------------------------------------------------------------- /examples/slice-pizza/proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | ADD . /proxy 4 | WORKDIR /proxy 5 | 6 | RUN pip install -r requirements.txt 7 | 8 | EXPOSE 5000 9 | 10 | -------------------------------------------------------------------------------- /examples/slice-pizza/proxy/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8 2 | charset-normalizer==2.0.12 3 | click==8.0.4 4 | Flask==2.0.3 5 | Flask-Cors==3.0.10 6 | idna==3.3 7 | itsdangerous==2.1.2 8 | Jinja2==3.1.1 9 | MarkupSafe==2.1.1 10 | requests==2.27.1 11 | six==1.16.0 12 | urllib3==1.26.9 13 | Werkzeug==2.0.3 14 | -------------------------------------------------------------------------------- /examples/slice-pizza/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.14-buster-slim 2 | 3 | ADD . /setup 4 | WORKDIR /setup 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/slice-pizza/setup/constants.js: -------------------------------------------------------------------------------- 1 | const DD_SITE = process.env.DD_SITE; 2 | const DD_API_KEY = process.env.DD_API_KEY; 3 | const DD_APP_KEY = process.env.DD_APP_KEY; 4 | 5 | const APP_NAME = process.env.APP_NAME; 6 | 7 | const BASE_URL = 8 | DD_SITE === 'datadoghq.eu' 9 | ? 'https://api.datadoghq.eu' 10 | : 'https://api.datadoghq.com'; 11 | 12 | const APP_URL = 'http://localhost:3002'; 13 | 14 | module.exports = { 15 | APP_URL, 16 | APP_NAME, 17 | BASE_URL, 18 | DD_SITE, 19 | DD_API_KEY, 20 | DD_APP_KEY 21 | }; 22 | -------------------------------------------------------------------------------- /examples/slice-pizza/setup/index.js: -------------------------------------------------------------------------------- 1 | const { v1 } = require('@datadog/datadog-api-client'); 2 | 3 | const { BASE_URL, DD_SITE } = require('./constants'); 4 | const createApp = require('./createDatadogApp'); 5 | 6 | async function main() { 7 | // eslint-disable-next-line no-console 8 | console.log('Configuring your account'); 9 | 10 | const configuration = v1.createConfiguration(); 11 | v1.setServerVariables(configuration, { 12 | site: DD_SITE 13 | }); 14 | 15 | // eslint-disable-next-line no-console 16 | console.log('Creating and configuring the Datadog App'); 17 | await createApp(); 18 | 19 | // eslint-disable-next-line no-console 20 | console.log(`You can now go to ${BASE_URL}`); 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /examples/slice-pizza/setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-slice-pizza-setup", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "build": "exit 0", 9 | "prepare": "exit 0", 10 | "start": "node index.js", 11 | "test": "echo TODO Add test script" 12 | }, 13 | "dependencies": { 14 | "@datadog/datadog-api-client": "^1.0.0-beta.6", 15 | "node-fetch": "2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/.data/tokens/.gitignore: -------------------------------------------------------------------------------- 1 | # Directory holding user tokens. Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/.data/users/.gitignore: -------------------------------------------------------------------------------- 1 | # Directory holding user and cart details. Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/.gitignore: -------------------------------------------------------------------------------- 1 | config.js 2 | planning.txt 3 | 4 | https/*.pem -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.14-buster-slim 2 | 3 | ADD . /api 4 | WORKDIR /api 5 | 6 | -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/example.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Configuration variables 3 | * --- Update this file with your details --- 4 | */ 5 | 6 | const environment = {}; 7 | 8 | environment.staging = { 9 | httpPort: 3000, 10 | httpsPort: 3001, 11 | envName: 'staging', 12 | hashingSecret: 'AwesomePizzaDelivery-dev', 13 | stripeSecretApi: '--stripe-api--', 14 | mailgun: { 15 | from: 'mailgun@--mailgun-domain--', 16 | domain: '--mailgun-domain--', 17 | privateApi: '--mailgun-api--' 18 | } 19 | }; 20 | 21 | environment.production = { 22 | httpPort: 5000, 23 | httpsPort: 5001, 24 | envName: 'production', 25 | hashingSecret: 'AwesomePizzaDelivery-prod', 26 | stripeSecretApi: '--stripe-api--', 27 | mailgun: { 28 | from: 'mailgun@--mailgun-domain--', 29 | domain: '--mailgun-domain--', 30 | privateApi: '--mailgun-api--' 31 | } 32 | }; 33 | 34 | // Determine set environment 35 | const currentEnv = 36 | typeof process.env.NODE_ENV === 'string' 37 | ? process.env.NODE_ENV.toLowerCase() 38 | : ''; 39 | 40 | // Check if a valid environment is specified 41 | const environmentToExport = 42 | typeof environment[currentEnv] === 'object' 43 | ? environment[currentEnv] 44 | : environment.staging; 45 | 46 | // Export environment 47 | module.exports = environmentToExport; 48 | -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/public/email-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/slice-life-pizzeria/public/email-example.jpg -------------------------------------------------------------------------------- /examples/slice-pizza/slice-life-pizzeria/public/pizza_icon15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/slice-pizza/slice-life-pizzeria/public/pizza_icon15.png -------------------------------------------------------------------------------- /examples/starter-kit/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/starter-kit/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/starter-kit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-starter-kit", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@datadog/ui-extensions-react": "0.32.2", 7 | "@datadog/ui-extensions-sdk": "0.32.2", 8 | "@types/node": "^14.14.14", 9 | "@types/react": "^17.0.0", 10 | "@types/react-dom": "^17.0.0", 11 | "milligram": "^1.4.1", 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0", 14 | "react-scripts": "5.0.1", 15 | "typeface-roboto": "^1.1.13", 16 | "typescript": "^4.1.3" 17 | }, 18 | "scripts": { 19 | "build": "react-scripts build", 20 | "eject": "react-scripts eject", 21 | "prepare": "exit 0", 22 | "start": "react-scripts start", 23 | "test": "react-scripts test --passWithNoTests --watchAll=false" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/starter-kit/public/_redirects: -------------------------------------------------------------------------------- 1 | /widget /index.html 200 2 | /modal /index.html 200 3 | /panel /index.html 200 -------------------------------------------------------------------------------- /examples/starter-kit/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/starter-kit/public/favicon.ico -------------------------------------------------------------------------------- /examples/starter-kit/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/starter-kit/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | import { setupModal } from './modal'; 3 | import { setupWidgetCtxMenu } from './widget-ctx-menu'; 4 | import { setupDashboardCogMenu } from './dashboard-cog-menu'; 5 | 6 | export default function setup() { 7 | const client = init(); 8 | setupModal(client); 9 | setupWidgetCtxMenu(client); 10 | setupDashboardCogMenu(client); 11 | 12 | const root = document.getElementById('root'); 13 | if (!root) { 14 | return; 15 | } 16 | root.innerHTML = ` 17 |
18 | The application controller is running in the background. 19 |
20 | Click here to open your widget 21 | `; 22 | } 23 | -------------------------------------------------------------------------------- /examples/starter-kit/src/controller/modal.ts: -------------------------------------------------------------------------------- 1 | import { DDClient, EventType } from '@datadog/ui-extensions-sdk'; 2 | 3 | export const setupModal = (client: DDClient) => { 4 | // listen for modal events 5 | client.events.on(EventType.MODAL_ACTION, () => { 6 | console.log('Confirmed!'); 7 | }); 8 | 9 | client.events.on(EventType.MODAL_CANCEL, () => { 10 | console.log('Denied!'); 11 | }); 12 | 13 | client.events.on(EventType.MODAL_CLOSE, definition => { 14 | console.log(`User exited modal ${definition.key}`); 15 | }); 16 | 17 | // listen for a custom event sent from modal IFrame 18 | client.events.onCustom('modal_button_click', (count: number) => { 19 | console.log(`The user has clicked the button ${count} times`); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/starter-kit/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica, arial, sans-serif; 3 | margin: 2em; 4 | } 5 | -------------------------------------------------------------------------------- /examples/starter-kit/src/index.tsx: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { 3 | case '/widget': { 4 | let widget = await import('./widget'); 5 | return widget.default(); 6 | } 7 | case '/panel': { 8 | let sidepanel = await import('./side-panel'); 9 | return sidepanel.default(); 10 | } 11 | case '/modal': { 12 | let modal = await import('./modal'); 13 | return modal.default(); 14 | } 15 | default: { 16 | let controller = await import('./controller'); 17 | return controller.default(); 18 | } 19 | } 20 | }; 21 | 22 | init(); 23 | export {}; 24 | -------------------------------------------------------------------------------- /examples/starter-kit/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/starter-kit/src/side-panel/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from '@datadog/ui-extensions-react'; 2 | import { init } from '@datadog/ui-extensions-sdk'; 3 | import './../index.css'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | const client = init(); 8 | 9 | function SidePanel() { 10 | const context = useContext(client); 11 | const args = context?.args; 12 | 13 | return ( 14 |
22 |

This side panel was opened programatically with these args

23 |
24 |

25 | {JSON.stringify(args)} 26 |

27 |
28 |
29 | ); 30 | } 31 | 32 | export default function render() { 33 | ReactDOM.render( 34 | {}, 35 | document.getElementById('root') 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /examples/starter-kit/src/widget/widget.css: -------------------------------------------------------------------------------- 1 | /* CSS files add styling rules to your content */ 2 | 3 | h1 { 4 | font-style: italic; 5 | color: #373fff; 6 | } 7 | -------------------------------------------------------------------------------- /examples/starter-kit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/stream/.env.example: -------------------------------------------------------------------------------- 1 | # Datadog Keys 2 | DD_SITE=datadoghq.eu || datadoghq.com # Choose one 3 | DD_API_KEY= 4 | DD_APP_KEY= 5 | 6 | # Project URLs - You can copy/paste them 7 | APP_UI_URL=http://localhost:3000 8 | SERVER_URL=http://localhost:3001 9 | ADMIN_UI_URL=http://localhost:3002 10 | 11 | -------------------------------------------------------------------------------- /examples/stream/.gitignore: -------------------------------------------------------------------------------- 1 | # Global 2 | .env 3 | -------------------------------------------------------------------------------- /examples/stream/README.md: -------------------------------------------------------------------------------- 1 | # Stream app 2 | 3 | ![The Datadog Dashboard of the Stream project](stream.png) 4 | 5 | ## Prerequesites 6 | 7 | - Docker 8 | - A Datadog Account with: 9 | - An API Key 10 | - An Application key 11 | 12 | ## Getting Started 13 | 14 | Clone the repo 15 | 16 | ``` 17 | $ git clone git@github.com:DataDog/apps.git 18 | ``` 19 | 20 | Change to Stream directory 21 | 22 | ``` 23 | $ cd ./cd examples/stream/ 24 | ``` 25 | 26 | Copy the example env file and add yours 27 | 28 | ``` 29 | $ cp .env.example .env 30 | ``` 31 | 32 | Build the Docker images 33 | 34 | ``` 35 | $ docker-compose build 36 | ``` 37 | 38 | Launch all your containers: 39 | 40 | - server: the back-end of the stream app 41 | - ui: the front-end of the stream (the Twitter like) 42 | - admin: the datadog app 43 | - setup: create your dashboard and your app on Datadog 44 | 45 | ``` 46 | docker-compose up 47 | ``` 48 | -------------------------------------------------------------------------------- /examples/stream/admin/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/stream/admin/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/stream/admin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /admin 4 | WORKDIR /admin 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/stream/admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-stream-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@datadog/datadog-api-client": "^1.0.0-beta.5", 7 | "@datadog/ui-extensions-react": "0.32.2", 8 | "@datadog/ui-extensions-sdk": "0.32.2", 9 | "@types/bootstrap": "^5.1.4", 10 | "@types/node": "^16.9.1", 11 | "@types/react": "^17.0.21", 12 | "@types/react-dom": "^17.0.0", 13 | "bootstrap": "^5.1.1", 14 | "react": "^18.0.0", 15 | "react-dom": "^18.0.0", 16 | "react-scripts": "5.0.1", 17 | "typeface-roboto": "^1.1.13", 18 | "typescript": "^4.4.3" 19 | }, 20 | "scripts": { 21 | "build": "react-scripts build", 22 | "eject": "react-scripts eject", 23 | "prepare": "exit 0", 24 | "start": "PORT=3002 react-scripts start", 25 | "test": "react-scripts test --passWithNoTests --watchAll=false" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/stream/admin/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 4, 4 | singleQuote: true, 5 | arrowParens: 'avoid', 6 | trailingComma: 'none' 7 | }; 8 | -------------------------------------------------------------------------------- /examples/stream/admin/src/auth-redirect/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { API_TOKEN_KEY } from '../api'; 3 | import { resolveAuthFlow } from '@datadog/ui-extensions-sdk'; 4 | 5 | export default function AuthRedirect() { 6 | React.useEffect(() => { 7 | const params = new URLSearchParams(window.location.search); 8 | 9 | const token = params.get('token'); 10 | 11 | if (token) { 12 | localStorage.setItem(API_TOKEN_KEY, token); 13 | 14 | resolveAuthFlow({ 15 | isAuthenticated: true 16 | }); 17 | } else { 18 | resolveAuthFlow({ 19 | isAuthenticated: false 20 | }); 21 | } 22 | }, []); 23 | 24 | return null; 25 | } 26 | -------------------------------------------------------------------------------- /examples/stream/admin/src/client.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk'; 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { API_TOKEN_KEY, API_URL } from './api'; 4 | 5 | const client = init({ 6 | // <<>>: Uncomment the code below to continue 7 | // authProvider: { 8 | // url: `${API_URL}/login?redirect=${window.location.origin}/auth-redirect`, 9 | // resolution: 'message', 10 | // authStateCallback: () => { 11 | // return localStorage.getItem(API_TOKEN_KEY) !== null; 12 | // } 13 | // } 14 | }); 15 | 16 | export default client; 17 | -------------------------------------------------------------------------------- /examples/stream/admin/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { setupWidgetCtxMenu } from './widget-ctx-menu'; 2 | import client from '../client'; 3 | 4 | export default function setup() { 5 | setupWidgetCtxMenu(client); 6 | 7 | const root = document.getElementById('root'); 8 | if (!root) { 9 | return; 10 | } 11 | root.innerHTML = ` 12 |
13 | The application controller is running in the background. 14 |
15 | Click here to open your widget 16 | `; 17 | } 18 | -------------------------------------------------------------------------------- /examples/stream/admin/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica, arial, sans-serif; 3 | margin: 2em; 4 | } 5 | -------------------------------------------------------------------------------- /examples/stream/admin/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import setup from './controller'; 5 | import BlocklistModal from './blocklist-modal'; 6 | import AccountPanel from './account-panel'; 7 | import Widget from './widget'; 8 | import AuthRedirect from './auth-redirect'; 9 | import 'bootstrap/dist/css/bootstrap.css'; 10 | 11 | const getContent = () => { 12 | switch (window.location.pathname) { 13 | case '/widget': { 14 | return ; 15 | } 16 | case '/blocklist-modal': { 17 | return ; 18 | } 19 | case '/account-panel': { 20 | return ; 21 | } 22 | case '/auth-redirect': { 23 | return ; 24 | } 25 | default: { 26 | setup(); 27 | } 28 | } 29 | }; 30 | 31 | ReactDOM.render( 32 | {getContent()}, 33 | document.getElementById('root') 34 | ); 35 | -------------------------------------------------------------------------------- /examples/stream/admin/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/stream/admin/src/widget/widget.css: -------------------------------------------------------------------------------- 1 | /* CSS files add styling rules to your content */ 2 | 3 | .row { 4 | margin-bottom: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /examples/stream/admin/src/workshop.ts: -------------------------------------------------------------------------------- 1 | export const WORKSHOP_COMPLETED = !!process.env.REACT_APP_WORKSHOP_COMPLETED; 2 | -------------------------------------------------------------------------------- /examples/stream/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/stream/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | server: 4 | build: server 5 | stdin_open: true 6 | tty: true 7 | env_file: 8 | - ./.env 9 | ports: 10 | - '3001:3001' 11 | volumes: 12 | - ./server:/server 13 | ui: 14 | build: ui 15 | stdin_open: true 16 | tty: true 17 | ports: 18 | - '3000:3000' 19 | volumes: 20 | - ./ui:/ui 21 | admin: 22 | build: admin 23 | stdin_open: true 24 | tty: true 25 | ports: 26 | - '3002:3002' 27 | volumes: 28 | - ./admin:/admin 29 | setup: 30 | build: setup 31 | stdin_open: true 32 | tty: true 33 | env_file: 34 | - ./.env 35 | volumes: 36 | - ./setup:/setup 37 | -------------------------------------------------------------------------------- /examples/stream/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | server: 4 | build: server 5 | command: yarn start 6 | env_file: 7 | - ./.env 8 | ports: 9 | - '3001:3001' 10 | ui: 11 | build: ui 12 | command: yarn start 13 | ports: 14 | - '3000:3000' 15 | admin: 16 | build: admin 17 | command: yarn start 18 | ports: 19 | - '3002:3002' 20 | setup: 21 | build: setup 22 | command: yarn start 23 | env_file: 24 | - ./.env 25 | -------------------------------------------------------------------------------- /examples/stream/server/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/stream/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /server 4 | WORKDIR /server 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/stream/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-stream-server", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "exit 0", 9 | "prepare": "exit 0", 10 | "start": "node index.js", 11 | "test": "echo TODO Add test script" 12 | }, 13 | "dependencies": { 14 | "@datadog/datadog-api-client": "^1.0.0-beta.5", 15 | "cors": "^2.8.5", 16 | "express": "^4.17.1", 17 | "express-history-api-fallback": "^2.2.1", 18 | "node-fetch": "^2.6.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/stream/server/users.js: -------------------------------------------------------------------------------- 1 | const allUsers = [ 2 | // Posting too many tweets 3 | { email: 'margaret@ecorp.com', role: 'post-spam', state: 'active' }, 4 | // Likely a bot crawling the api 5 | { email: 'lucious14@hotmail.com', role: 'get-spam', state: 'active' }, 6 | { email: 'chelsea.rolfson@yahoo.com', role: 'user', state: 'active' }, 7 | { email: 'tschiller@yahoo.com', role: 'user', state: 'active' }, 8 | { email: 'ptillman@jast.com', role: 'user', state: 'active' }, 9 | { email: 'oberbrunner.hailie@hotmail.com', role: 'user', state: 'active' }, 10 | { email: 'doyle.ollie@gmail.com', role: 'user', state: 'active' }, 11 | { email: 'oconner.rogers@yahoo.com', role: 'user', state: 'active' }, 12 | { email: 'nfeeney@langworth.com', role: 'user', state: 'active' }, 13 | { email: 'nadia10@maggio.com', role: 'user', state: 'active' } 14 | ]; 15 | 16 | const getUser = email => { 17 | return allUsers.find(u => u.email === email); 18 | }; 19 | 20 | module.exports = { 21 | allUsers, 22 | getUser 23 | }; 24 | -------------------------------------------------------------------------------- /examples/stream/setup/.gitignore: -------------------------------------------------------------------------------- 1 | # Global 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/stream/setup/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/stream/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /setup 4 | WORKDIR /setup 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/stream/setup/constants.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = 2 | process.env.DD_SITE === 'datadoghq.eu' 3 | ? 'https://api.datadoghq.eu' 4 | : 'https://api.datadoghq.com'; 5 | 6 | module.exports = BASE_URL; 7 | -------------------------------------------------------------------------------- /examples/stream/setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-stream-setup", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "Workshop setup", 6 | "main": "setup-workshop.js", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "exit 0", 10 | "prepare": "exit 0", 11 | "start": "node setup-workshop.js", 12 | "test": "echo TODO Add test script" 13 | }, 14 | "dependencies": { 15 | "@datadog/datadog-api-client": "^1.0.0-beta.5", 16 | "node-fetch": "^2.6.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/stream/setup/setup-workshop.js: -------------------------------------------------------------------------------- 1 | const { v1 } = require('@datadog/datadog-api-client'); 2 | 3 | const createApp = require('./create-app'); 4 | const createDashboard = require('./create-dashboard'); 5 | 6 | const init = async () => { 7 | // eslint-disable-next-line no-console 8 | console.log('Configuring your account'); 9 | 10 | const configuration = v1.createConfiguration(); 11 | v1.setServerVariables(configuration, { 12 | site: process.env.DD_SITE 13 | }); 14 | 15 | const appId = await createApp(configuration); 16 | await createDashboard(configuration, appId); 17 | 18 | // eslint-disable-next-line no-console 19 | console.log( 20 | 'Your account has been configured with a dashbooard and sample datadog app, you are ready to go!' 21 | ); 22 | }; 23 | 24 | init().catch(console.error); 25 | -------------------------------------------------------------------------------- /examples/stream/stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/stream/stream.png -------------------------------------------------------------------------------- /examples/stream/ui/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/stream/ui/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | CHANGELOG.md 3 | dist 4 | -------------------------------------------------------------------------------- /examples/stream/ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13-buster-slim 2 | 3 | ADD . /ui 4 | WORKDIR /ui 5 | 6 | RUN yarn 7 | 8 | -------------------------------------------------------------------------------- /examples/stream/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-example-stream-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/react": "^13.0.0", 7 | "@testing-library/user-event": "^14.0.4", 8 | "@types/bootstrap": "^5.0.17", 9 | "@types/node": "^16.9.1", 10 | "@types/react": "^17.0.0", 11 | "@types/react-dom": "^17.0.0", 12 | "bootstrap": "^5.0.2", 13 | "react": "^18.0.0", 14 | "react-dom": "^18.0.0", 15 | "react-scripts": "5.0.1", 16 | "typescript": "^4.1.2" 17 | }, 18 | "scripts": { 19 | "build": "react-scripts build", 20 | "eject": "react-scripts eject", 21 | "prepare": "exit 0", 22 | "start": "react-scripts start", 23 | "test": "react-scripts test --passWithNoTests --watchAll=false" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/stream/ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css'; 2 | import ReactDOM from 'react-dom'; 3 | import React from 'react'; 4 | 5 | import { Login } from './Login'; 6 | import App from './Stream'; 7 | 8 | const getContent = () => { 9 | switch (window.location.pathname) { 10 | case '/login': { 11 | return ; 12 | } 13 | default: { 14 | return ; 15 | } 16 | } 17 | }; 18 | 19 | ReactDOM.render( 20 | {getContent()}, 21 | document.getElementById('root') 22 | ); 23 | -------------------------------------------------------------------------------- /examples/stream/ui/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/stream/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/README.md: -------------------------------------------------------------------------------- 1 | # Datadog App 2 | 3 | ## Widget context menu with 3rd-party data 4 | 5 | This App provides a widget context menu that interacts with 3rd-party data. 6 | 3rd-party data is data provided by something external to Datadog (like your service or another service). 7 | It can be used as a starting point for building out a real-world App on the Datadog Developer Platform. 8 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/public/_redirects: -------------------------------------------------------------------------------- 1 | /modal /index.html 200 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/examples/widget-context-menu-with-3rd-party-data/public/favicon.ico -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/3rd-party/host/index.ts: -------------------------------------------------------------------------------- 1 | export { getHostInformation, parseHostInformation } from './host'; 2 | export type { HostInformation } from './host'; 3 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/3rd-party/login/index.ts: -------------------------------------------------------------------------------- 1 | export { renderLogin } from './login'; 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/3rd-party/user/index.ts: -------------------------------------------------------------------------------- 1 | export { getUser, loginUser } from './user'; 2 | export type { User } from './user'; 3 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export { client } from './client'; 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/controller/controller.tsx: -------------------------------------------------------------------------------- 1 | import { DDClient } from '@datadog/ui-extensions-sdk'; 2 | import * as React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { useSetupWidgetContextMenu } from './widget-context-menu'; 5 | 6 | type ControllerProps = { 7 | client: DDClient; 8 | }; 9 | 10 | /** 11 | * This component renders the main controller. 12 | * The main controller responds to the initial handshake from Datadog and sets up any App-wide behavior. 13 | * 14 | * @see https://github.com/DataDog/apps/blob/-/docs/en/programming-model.md#main-controller-iframe 15 | */ 16 | function Controller(props: ControllerProps): JSX.Element { 17 | useSetupWidgetContextMenu(props.client); 18 | 19 | return ( 20 | <> 21 |
The application controller is running in the background.
22 | 23 | Click here to open your custom widget 24 | 25 | 26 | ); 27 | } 28 | 29 | function renderController(client: DDClient) { 30 | ReactDOM.render( 31 | 32 | 33 | , 34 | document.getElementById('root') 35 | ); 36 | } 37 | 38 | export { renderController }; 39 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/controller/index.ts: -------------------------------------------------------------------------------- 1 | export { renderController } from './controller'; 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/index.ts: -------------------------------------------------------------------------------- 1 | import { client } from './client'; 2 | import { renderController } from './controller'; 3 | import { renderLogin } from './3rd-party/login'; 4 | import { renderModal } from './modal'; 5 | 6 | switch (window.location.pathname) { 7 | /** 8 | * This authentication route is part of this App, 9 | * but the system is designed to be able to leverage a preexisting authentication route if you have access to it. 10 | * If you already have an authentication route, 11 | * this route should be removed. 12 | */ 13 | case '/login': 14 | renderLogin(client); 15 | break; 16 | 17 | case '/modal': 18 | renderModal(client); 19 | break; 20 | 21 | default: 22 | renderController(client); 23 | break; 24 | } 25 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/modal/index.ts: -------------------------------------------------------------------------------- 1 | export { renderModal } from './modal'; 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/widget-context-menu-with-3rd-party-data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "react-jsx", 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "es5" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": { 3 | "publish": { 4 | "verifyAccess": false 5 | } 6 | }, 7 | "npmClient": "yarn", 8 | "packages": ["packages/*"], 9 | "version": "independent" 10 | } 11 | -------------------------------------------------------------------------------- /packages/create-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off', 4 | 'import/no-extraneous-dependencies': [ 5 | 'error', 6 | { 7 | devDependencies: true 8 | } 9 | ] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/create-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /bin 14 | /build 15 | *.tgz 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /packages/create-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datadog/create-app", 3 | "version": "2.0.1", 4 | "description": "npm init package to create a Datadog App", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/DataDog/apps.git", 8 | "directory": "packages/create-app" 9 | }, 10 | "scripts": { 11 | "build": "yarn build:compile && yarn build:copy-template", 12 | "build:compile": "ncc build src/index.ts --minify --out bin", 13 | "build:copy-template": "cp -R src/template/. bin/template", 14 | "prepare": "yarn build", 15 | "test": "yarn test:acceptance", 16 | "test:acceptance": "./src/index.acceptance.sh", 17 | "watch": "ncc build --out bin --watch" 18 | }, 19 | "license": "Apache-2.0", 20 | "devDependencies": { 21 | "@types/tar": "6.1.1", 22 | "@vercel/ncc": "0.38.1", 23 | "clipanion": "3.1.0", 24 | "got": "11.8.3", 25 | "handlebars": "4.7.7", 26 | "pkg-install": "1.0.0", 27 | "tar": "6.1.11", 28 | "typanion": "3.7.1" 29 | }, 30 | "bin": "bin/index.js", 31 | "files": [ 32 | "bin", 33 | "bin/template/.gitignore" 34 | ], 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/create-app/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as clipanion from 'clipanion'; 4 | import * as process from 'process'; 5 | 6 | import * as packageJSON from '../package.json'; 7 | 8 | import * as initialize from './initialize'; 9 | 10 | async function main(): Promise { 11 | const [node, program, ...argv] = process.argv; 12 | 13 | const cli = new clipanion.Cli({ 14 | binaryLabel: packageJSON.name, 15 | binaryName: `${node} ${program}`, 16 | binaryVersion: packageJSON.version, 17 | enableCapture: true 18 | }); 19 | 20 | cli.register(clipanion.Builtins.HelpCommand); 21 | cli.register(clipanion.Builtins.VersionCommand); 22 | cli.register(initialize.Command); 23 | return cli.runExit(argv); 24 | } 25 | 26 | main().catch((error: Error): void => { 27 | console.error(error); 28 | }); 29 | 30 | export {}; 31 | -------------------------------------------------------------------------------- /packages/create-app/src/template/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /packages/create-app/src/template/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/create-app/src/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadog-app-template", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@datadog/ui-extensions-react": "0.30.1", 7 | "@datadog/ui-extensions-sdk": "0.30.1", 8 | "@types/node": "^14.14.14", 9 | "@types/react": "^17.0.0", 10 | "@types/react-dom": "^17.0.0", 11 | "milligram": "^1.4.1", 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0", 14 | "react-scripts": "5.0.1", 15 | "typeface-roboto": "^1.1.13", 16 | "typescript": "^4.5.4" 17 | }, 18 | "scripts": { 19 | "build": "react-scripts build", 20 | "eject": "react-scripts eject", 21 | "prepare": "exit 0", 22 | "start": "react-scripts start", 23 | "test": "react-scripts test --passWithNoTests --watchAll=false" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/create-app/src/template/public/_redirects.handlebars: -------------------------------------------------------------------------------- 1 | {{#if customWidget}}/custom-widget /index.html 200{{/if}}{{#if modal}} 2 | /modal /index.html 200{{/if}}{{#if sidePanel}} 3 | /side-panel /index.html 200{{/if}} 4 | -------------------------------------------------------------------------------- /packages/create-app/src/template/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/apps/919a49d4e388f9ee5aabbb74385d9a3e32d183e4/packages/create-app/src/template/public/favicon.ico -------------------------------------------------------------------------------- /packages/create-app/src/template/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/controller/index.ts.handlebars: -------------------------------------------------------------------------------- 1 | import { init } from '@datadog/ui-extensions-sdk';{{#if dashboardCogMenu}} 2 | import { setupDashboardCogMenu } from './dashboard-cog-menu';{{/if}}{{#if modal}} 3 | import { setupModal } from './modal';{{/if}}{{#if widgetContextMenu}} 4 | import { setupWidgetContextMenu } from './widget-context-menu';{{/if}} 5 | 6 | export default function setup() { 7 | const client = init();{{#if dashboardCogMenu}} 8 | setupDashboardCogMenu(client);{{/if}}{{#if modal}} 9 | setupModal(client);{{/if}}{{#if widgetContextMenu}} 10 | setupWidgetContextMenu(client);{{/if}} 11 | 12 | const root = document.getElementById('root'); 13 | if (!root) { 14 | return; 15 | } 16 | 17 | root.innerHTML = ` 18 |
19 | The application controller is running in the background. 20 |
{{#if customWidget}} 21 | Click here to open your custom widget{{/if}} 22 | `; 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/controller/modal.ts: -------------------------------------------------------------------------------- 1 | import { DDClient, EventType } from '@datadog/ui-extensions-sdk'; 2 | 3 | export const setupModal = (client: DDClient) => { 4 | // listen for modal events 5 | client.events.on(EventType.MODAL_ACTION, () => { 6 | console.log('Confirmed!'); 7 | }); 8 | 9 | client.events.on(EventType.MODAL_CANCEL, () => { 10 | console.log('Denied!'); 11 | }); 12 | 13 | client.events.on(EventType.MODAL_CLOSE, definition => { 14 | console.log(`User exited modal ${definition.key}`); 15 | }); 16 | 17 | // listen for a custom event sent from modal IFrame 18 | client.events.onCustom('modal_button_click', (count: number) => { 19 | console.log(`The user has clicked the button ${count} times`); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/custom-widget/custom-widget.css: -------------------------------------------------------------------------------- 1 | /* CSS files add styling rules to your content */ 2 | 3 | h1 { 4 | font-style: italic; 5 | color: #373fff; 6 | } 7 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica, arial, sans-serif; 3 | margin: 2em; 4 | } 5 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/index.tsx.handlebars: -------------------------------------------------------------------------------- 1 | const init = async () => { 2 | switch (window.location.pathname) { {{#if customWidget}} 3 | case '/custom-widget': { 4 | let customWidget = await import('./custom-widget'); 5 | return customWidget.default(); 6 | }{{/if}}{{#if modal}} 7 | case '/modal': { 8 | let modal = await import('./modal'); 9 | return modal.default(); 10 | }{{/if}}{{#if sidePanel}} 11 | case '/side-panel': { 12 | let sidePanel = await import('./side-panel'); 13 | return sidePanel.default(); 14 | }{{/if}} 15 | default: { 16 | let controller = await import('./controller'); 17 | return controller.default(); 18 | } 19 | } 20 | }; 21 | 22 | init(); 23 | export {}; 24 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/create-app/src/template/src/side-panel/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from '@datadog/ui-extensions-react'; 2 | import { init } from '@datadog/ui-extensions-sdk'; 3 | import './../index.css'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | const client = init(); 8 | 9 | function SidePanel() { 10 | const context = useContext(client); 11 | const args = context?.args; 12 | 13 | return ( 14 |
22 |

This side panel was opened programatically with these args

23 |
24 |

25 | {JSON.stringify(args)} 26 |

27 |
28 |
29 | ); 30 | } 31 | 32 | export default function render() { 33 | ReactDOM.render( 34 | {}, 35 | document.getElementById('root') 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/create-app/src/template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "react-jsx", 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "target": "es5" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/create-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "outDir": "bin", 6 | "target": "es2015" 7 | }, 8 | "exclude": ["**/node_modules/**/*", "src/template"], 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/framepost/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.less] 2 | indent_size = 4 3 | indent_style = space 4 | [*.js] 5 | indent_size = 4 6 | indent_style = space 7 | [*.jsx] 8 | indent_size = 4 9 | indent_style = space 10 | [*.ts] 11 | indent_size = 4 12 | indent_style = space 13 | [*.tsx] 14 | indent_size = 4 15 | indent_style = space 16 | [*.mdx] 17 | indent_size = 4 18 | indent_style = space 19 | [Rakefile] 20 | indent_size = 2 21 | indent_style = space 22 | -------------------------------------------------------------------------------- /packages/framepost/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | module.exports = { 3 | globals: { 4 | it: true, 5 | expect: true, 6 | test: true 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/framepost/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /packages/framepost/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @datadog/framepost 2 | 3 | ## 0.4.1 4 | 5 | ### Patch Changes 6 | 7 | - 5d878a0: update dependencies 8 | 9 | ## 0.4.0 10 | 11 | ### Minor Changes 12 | 13 | - d2430a4: Build framepost for import by both the browser and node 14 | 15 | ## 0.3.1 16 | 17 | ### Patch Changes 18 | 19 | - 1f2d674: Bump `typescript` to 4.5.4 and remove from explicit `devDependencies`. 20 | 21 | We move the dependency up to the root `package.json` for consistency across projects. 22 | 23 | ## 0.3.0 24 | 25 | ### Minor Changes 26 | 27 | - 7e959d1: Ensure full shutdown of requests on framepost client destroy. Additional errors may now be thrown from request handlers in rare side-cases. This should mainly not affect the SDK, but we are bumping the minor version to be safe. 28 | 29 | ## 0.2.4 30 | 31 | ### Patch Changes 32 | 33 | - ca3c3ae: Import framepost into monorepo 34 | -------------------------------------------------------------------------------- /packages/framepost/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Datadog, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/framepost/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | Bundle is served at 10 | http://localhost:8080/dist/framepost.min.js 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/framepost/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | testPathIgnorePatterns: ['/dist'] 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framepost/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 4, 4 | singleQuote: true, 5 | arrowParens: 'avoid', 6 | trailingComma: 'none' 7 | }; 8 | -------------------------------------------------------------------------------- /packages/framepost/sandbox/child/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FramePost sandbox: Child 5 | 6 | 7 |

FramePost sandbox: Child

8 | 9 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/framepost/scripts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 0, 4 | 'import/no-extraneous-dependencies': [ 5 | 'error', 6 | { 7 | devDependencies: true, 8 | optionalDependencies: true, 9 | peerDependencies: true 10 | } 11 | ] 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /packages/framepost/scripts/sandbox.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const serveStatic = require('serve-static'); 3 | const path = require('path'); 4 | 5 | const PARENT_PORT = 8000; 6 | const CHILD_PORT = 8001; 7 | 8 | express() 9 | .use(serveStatic(path.resolve(__dirname, '..', 'sandbox/parent'))) 10 | .use(serveStatic(path.resolve(__dirname, '..'))) 11 | .listen(PARENT_PORT, () => { 12 | console.log(`Parent app served at http://localhost:${PARENT_PORT}`); 13 | }); 14 | 15 | express() 16 | .use(serveStatic(path.resolve(__dirname, '..', 'sandbox/child'))) 17 | .use(serveStatic(path.resolve(__dirname, '..'))) 18 | .listen(CHILD_PORT, () => { 19 | console.log(`Child app served at http://localhost:${CHILD_PORT}`); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/framepost/src/constants.ts: -------------------------------------------------------------------------------- 1 | export enum MessageType { 2 | CHANNEL_INIT = 'channel_init', 3 | EVENT = 'event', 4 | REQUEST = 'request', 5 | RESPONSE = 'response', 6 | ERROR_RESPONSE = 'error_response' 7 | } 8 | 9 | export enum MessageAPIVersion { 10 | v1 = 'framepost/v1' 11 | } 12 | 13 | export enum ProfileEventType { 14 | POST_MESSAGE = 'post_message', 15 | RECEIVE_MESSAGE = 'receive_message' 16 | } 17 | 18 | export enum TransactionDirection { 19 | UP = 'up', 20 | DOWN = 'down' 21 | } 22 | 23 | export enum SerializationType { 24 | NONE = 'none', 25 | ERROR = 'error' 26 | } 27 | 28 | export const DEFAULT_REQUEST_TIMEOUT = 20000; 29 | 30 | export const REQUEST_KEY_GET_PROFILE = 'framepost_get_profile'; 31 | -------------------------------------------------------------------------------- /packages/framepost/src/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Typed errors allow consumer to distinguish failure cases 3 | */ 4 | 5 | export class HandshakeTimeoutError extends Error { 6 | constructor() { 7 | super('Handshake timed out'); 8 | 9 | /** 10 | * Because we are targeting es5 in complilation, instanceOf checks won't work 11 | * with the resulting error types. See https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/ 12 | * TODO: Can we upgrade the compilation target? What do we need to support in iframes? 13 | */ 14 | Object.setPrototypeOf(this, HandshakeTimeoutError.prototype); 15 | 16 | this.name = 'HandshakeTimeoutError'; 17 | } 18 | } 19 | 20 | export class RequestTimeoutError extends Error { 21 | constructor() { 22 | super('Request timed out'); 23 | 24 | Object.setPrototypeOf(this, RequestTimeoutError.prototype); 25 | 26 | this.name = 'RequestTimeoutError'; 27 | } 28 | } 29 | 30 | export class ClientDestroyedError extends Error { 31 | constructor() { 32 | super('Client destroyed'); 33 | 34 | Object.setPrototypeOf(this, ClientDestroyedError.prototype); 35 | 36 | this.name = 'ClientDestroyedError'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/framepost/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ChildClient } from './child'; 2 | export { ParentClient } from './parent'; 3 | 4 | export * from './types'; 5 | export * from './errors'; 6 | -------------------------------------------------------------------------------- /packages/framepost/src/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | export interface Logger { 3 | log(message: string): void; 4 | error(message: string): void; 5 | } 6 | 7 | export const getLogger = (prefix: string, debug: boolean): Logger => { 8 | if (debug) { 9 | return { 10 | log(message: string) { 11 | return console.log(`${prefix}: ${message}`); 12 | }, 13 | error(message: string) { 14 | return console.error(`${prefix}: ${message}`); 15 | } 16 | }; 17 | } else { 18 | return { 19 | log() {}, 20 | error() {} 21 | }; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /packages/framepost/src/profiler.ts: -------------------------------------------------------------------------------- 1 | import { ProfileEventType } from './constants'; 2 | import type { MessageProfileEvent, Message } from './types'; 3 | 4 | export interface Profiler { 5 | logEvent(type: ProfileEventType, message: Message): void; 6 | getEvents(): MessageProfileEvent[]; 7 | } 8 | 9 | export const getProfiler = (profile: boolean): Profiler => { 10 | const events: MessageProfileEvent[] = []; 11 | 12 | return { 13 | logEvent(type: ProfileEventType, message: Message) { 14 | if (profile) { 15 | events.push({ 16 | type, 17 | message, 18 | date: new Date() 19 | }); 20 | } 21 | }, 22 | getEvents() { 23 | return events; 24 | } 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/framepost/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "target": "es5" 8 | }, 9 | "exclude": ["dist"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/framepost/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | target: ['web', 'es5'], 6 | devtool: 'source-map', 7 | entry: './src/index.ts', 8 | output: { 9 | filename: 'framepost.min.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | library: 'framepost', 12 | libraryTarget: 'umd', 13 | globalObject: 'this' 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | loader: 'ts-loader' 20 | } 21 | ] 22 | }, 23 | resolve: { 24 | extensions: ['.ts', '.tsx', '.js'] 25 | }, 26 | devServer: { 27 | clientLogLevel: 'warning', 28 | open: true, 29 | historyApiFallback: true, 30 | stats: 'errors-only' 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /packages/ui-extensions-react/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | -------------------------------------------------------------------------------- /packages/ui-extensions-react/README.md: -------------------------------------------------------------------------------- 1 | # DataDog UI Extensions React Helpers 2 | 3 | This packages provides helpers for using the DataDog UI Extensions SDK with React. It is under active development and is subject to breaking changes at any time. 4 | 5 | ## Installation 6 | 7 | Install both this package and the SDK: 8 | 9 | ```Console 10 | $ yarn add @datadog/ui-extensions-react @datadog/ui-extensions-sdk 11 | ``` 12 | 13 | ## Usage 14 | 15 | See the [official documentation][] to get started with Apps. 16 | 17 | See the [examples][] for some uses of this package. 18 | 19 | There is also [API documentation][] available. 20 | 21 | [api documentation]: https://datadoghq.dev/apps/modules/_datadog_ui_extensions_react.html 22 | [official documentation]: https://docs.datadoghq.com/developers/datadog_apps 23 | [examples]: https://github.com/DataDog/apps/tree/master/examples 24 | -------------------------------------------------------------------------------- /packages/ui-extensions-react/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom' 4 | }; 5 | -------------------------------------------------------------------------------- /packages/ui-extensions-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-context'; 2 | export * from './use-custom-widget-option'; 3 | export * from './use-custom-widget-option-boolean'; 4 | export * from './use-custom-widget-option-string'; 5 | export * from './use-template-variable'; 6 | -------------------------------------------------------------------------------- /packages/ui-extensions-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "target": "es5" 8 | }, 9 | "exclude": ["dist"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ui-extensions-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | 4 | module.exports = (env, options) => { 5 | const config = { 6 | devtool: 'source-map', 7 | externals: { 8 | '@datadog/ui-extensions-sdk': '@datadog/ui-extensions-sdk', 9 | react: 'react' 10 | }, 11 | target: ['web', 'es5'], 12 | entry: './src/index.ts', 13 | output: { 14 | filename: 'ui-extensions-react.min.js', 15 | path: path.resolve(__dirname, 'dist'), 16 | library: 'DD_REACT', 17 | libraryTarget: 'umd' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.tsx?$/, 23 | loader: 'ts-loader' 24 | } 25 | ] 26 | }, 27 | plugins: [], 28 | resolve: { 29 | extensions: ['.ts', '.tsx', '.js'] 30 | }, 31 | devServer: { 32 | clientLogLevel: 'warning', 33 | open: true, 34 | historyApiFallback: true, 35 | stats: 'errors-only' 36 | } 37 | }; 38 | 39 | if (options.mode === 'production') { 40 | config.plugins.push(new CleanWebpackPlugin()); 41 | } 42 | 43 | return config; 44 | }; 45 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/README.md: -------------------------------------------------------------------------------- 1 | # DataDog UI Extensions SDK 2 | 3 | This packages provides a javascript SDK for DataDog UI Extensions. It is under active development and is subject to breaking changes at any time. 4 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | Bundle is served at 10 | http://localhost:8080/dist/ui-extensions-sdk.min.js 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | globals: { 5 | SDK_VERSION: '0.1.0' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/sandbox/child/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Datadog UI Extensions SDK Sandbox: Child 5 | 6 | 7 | 8 |

Datadog UI Extensions SDK Sandbox: Child

9 |
Link to public dashboard: 
10 | 11 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/sandbox/parent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Datadog Ui Extensions SDK Sandbox: Parent 5 | 6 | 7 | 8 |

Datadog Ui Extensions SDK Sandbox: Parent

9 | 14 | 15 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/scripts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 0, 4 | 'import/no-extraneous-dependencies': [ 5 | 'error', 6 | { 7 | devDependencies: true, 8 | optionalDependencies: true, 9 | peerDependencies: true 10 | } 11 | ] 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/scripts/sandbox.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const serveStatic = require('serve-static'); 3 | const path = require('path'); 4 | 5 | const PARENT_PORT = 8000; 6 | const CHILD_PORT = 8001; 7 | 8 | express() 9 | .use(serveStatic(path.resolve(__dirname, '..', 'sandbox/parent'))) 10 | .use(serveStatic(path.resolve(__dirname, '..'))) 11 | .listen(PARENT_PORT, () => { 12 | console.log(`Parent app served at http://localhost:${PARENT_PORT}`); 13 | }); 14 | 15 | express() 16 | .use(serveStatic(path.resolve(__dirname, '..', 'sandbox/child'))) 17 | .use(serveStatic(path.resolve(__dirname, '..'))) 18 | .listen(CHILD_PORT, () => { 19 | console.log(`Child app served at http://localhost:${CHILD_PORT}`); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/config/config.test.ts: -------------------------------------------------------------------------------- 1 | import { RequestType } from '../constants'; 2 | import { MockClient } from '../utils/testUtils'; 3 | 4 | import { DDConfigClient } from './config'; 5 | 6 | let mockClient: MockClient; 7 | let configClient: DDConfigClient; 8 | 9 | beforeEach(() => { 10 | mockClient = new MockClient(); 11 | configClient = new DDConfigClient(mockClient as any); 12 | }); 13 | 14 | describe('DDConfigClient', () => { 15 | test('can return app config', async () => { 16 | mockClient.framePostClient.request = jest.fn(); 17 | 18 | await configClient.getOrgConfig(); 19 | 20 | expect(mockClient.framePostClient.request).toHaveBeenCalledWith( 21 | RequestType.GET_ORG_CONFIG, 22 | undefined 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { RequestType } from '../constants'; 2 | import { 3 | OrgConfig, 4 | ContextClient, 5 | LoggerClient, 6 | RequestClient 7 | } from '../types'; 8 | 9 | export class DDConfigClient { 10 | private readonly client: ContextClient & LoggerClient & RequestClient; 11 | 12 | constructor(client: ContextClient & LoggerClient & RequestClient) { 13 | this.client = client; 14 | } 15 | 16 | async getOrgConfig(): Promise { 17 | return this.client.request(RequestType.GET_ORG_CONFIG); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/dashboard/dashboard-custom-widget/dashboard-custom-widget.ts: -------------------------------------------------------------------------------- 1 | import { RequestType, FeatureType } from '../../constants'; 2 | import { DDFeatureClient } from '../../shared/feature-client'; 3 | import type { 4 | ContextClient, 5 | LoggerClient, 6 | RequestClient, 7 | WidgetOptionItem 8 | } from '../../types'; 9 | 10 | export class DDDashboardCustomWidgetClient extends DDFeatureClient { 11 | constructor(client: ContextClient & LoggerClient & RequestClient) { 12 | super(client, FeatureType.DASHBOARD_CUSTOM_WIDGET); 13 | } 14 | 15 | async updateOptions(newOptions: WidgetOptionItem[]) { 16 | const { widget } = await this.getContext(); 17 | if (widget?.definition && widget?.id) { 18 | return this.sendRequest( 19 | RequestType.DASHBOARD_CUSTOM_WIDGET_OPTIONS_UPDATE, 20 | { 21 | customWidgetKey: widget.definition.custom_widget_key, 22 | customWidgetID: widget.id, 23 | newOptions 24 | } 25 | ); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/features/dashboard-cog-menu.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, EventType } from '../constants'; 2 | import { Feature } from '../types'; 3 | 4 | export const dashboardCogMenu: Feature = { 5 | type: FeatureType.DASHBOARD_COG_MENU, 6 | events: [EventType.DASHBOARD_COG_MENU_CLICK] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/features/dashboard-custom-widget.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, EventType } from '../constants'; 2 | import { Feature } from '../types'; 3 | 4 | export const dashboardCustomWidget: Feature = { 5 | type: FeatureType.DASHBOARD_CUSTOM_WIDGET, 6 | events: [ 7 | EventType.DASHBOARD_CUSTOM_WIDGET_OPTIONS_CHANGE, 8 | EventType.DASHBOARD_TEMPLATE_VAR_CHANGE, 9 | EventType.DASHBOARD_TIMEFRAME_CHANGE, 10 | EventType.DASHBOARD_CURSOR_CHANGE, 11 | EventType.WIDGET_SETTINGS_MENU_CLICK 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/features/index.ts: -------------------------------------------------------------------------------- 1 | import { dashboardCogMenu } from './dashboard-cog-menu'; 2 | import { dashboardCustomWidget } from './dashboard-custom-widget'; 3 | import { modals } from './modals'; 4 | import { sidePanels } from './side-panels'; 5 | import { widgetContextMenu } from './widget-context-menu'; 6 | 7 | export const features = [ 8 | dashboardCogMenu, 9 | dashboardCustomWidget, 10 | modals, 11 | sidePanels, 12 | widgetContextMenu 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/features/modals.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, EventType } from '../constants'; 2 | import { Feature } from '../types'; 3 | 4 | export const modals: Feature = { 5 | type: FeatureType.MODALS, 6 | events: [ 7 | EventType.MODAL_CLOSE, 8 | EventType.MODAL_CANCEL, 9 | EventType.MODAL_ACTION 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/features/side-panels.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, EventType } from '../constants'; 2 | import { Feature } from '../types'; 3 | 4 | export const sidePanels: Feature = { 5 | type: FeatureType.SIDE_PANELS, 6 | events: [EventType.SIDE_PANEL_CLOSE] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/features/widget-context-menu.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, EventType } from '../constants'; 2 | import { Feature } from '../types'; 3 | 4 | export const widgetContextMenu: Feature = { 5 | type: FeatureType.WIDGET_CONTEXT_MENU, 6 | events: [EventType.WIDGET_CONTEXT_MENU_CLICK] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | import { DDClient } from './client/client'; 2 | import { Context, ClientOptions } from './types'; 3 | 4 | export { resolveAuthFlow } from './auth/auth'; 5 | 6 | let client: DDClient; 7 | 8 | /** 9 | * Initializes a client, or returns an existing one if already initialized. User can provide an optional 10 | * callback to b de executed with app context data when it is sent from the parent. 11 | */ 12 | export const init = ( 13 | options?: ClientOptions, 14 | callback?: (context: Context) => void 15 | ): DDClient => { 16 | if (!client) { 17 | client = new DDClient(options); 18 | } 19 | 20 | if (callback) { 21 | client.getContext().then(callback); 22 | } 23 | 24 | return client as DDClient; 25 | }; 26 | 27 | export * from './types'; 28 | export * from './constants'; 29 | 30 | export { DDClient }; 31 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/location/location.ts: -------------------------------------------------------------------------------- 1 | import { RequestType } from '../constants'; 2 | import { RequestClient } from '../types'; 3 | 4 | export class DDLocationClient { 5 | private readonly client: RequestClient; 6 | 7 | constructor(client: RequestClient) { 8 | this.client = client; 9 | } 10 | 11 | async goTo(url: string) { 12 | return this.client.request( 13 | RequestType.NAVIGATE_TOP, 14 | { 15 | url 16 | } 17 | ); 18 | } 19 | } 20 | 21 | export interface NavigateTopRequest { 22 | url: string; 23 | } 24 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/modal/modal.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, RequestType } from '../constants'; 2 | import { DDFeatureClient } from '../shared/feature-client'; 3 | import type { 4 | ContextClient, 5 | LoggerClient, 6 | ModalDefinition, 7 | RequestClient 8 | } from '../types'; 9 | import { validateKey } from '../utils/utils'; 10 | 11 | export class DDModalClient extends DDFeatureClient { 12 | constructor(client: ContextClient & LoggerClient & RequestClient) { 13 | super(client, FeatureType.MODALS); 14 | } 15 | 16 | /** 17 | * Opens a modal, given a full modal definition or the key of a modal 18 | * definition pre-defined in the app manifest 19 | */ 20 | async open(definition: ModalDefinition, args?: unknown) { 21 | if (validateKey(definition)) { 22 | return this.sendRequest(RequestType.OPEN_MODAL, { 23 | definition, 24 | args 25 | }); 26 | } 27 | } 28 | 29 | /** 30 | * Closes any active modals opened by this app. If a key is provided, it will only close the modal 31 | * if it matches the provided key. 32 | */ 33 | async close(key?: string) { 34 | return this.sendRequest(RequestType.CLOSE_MODAL, key); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/notification/notification.test.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, RequestType } from '../constants'; 2 | import { mockContext, MockClient } from '../utils/testUtils'; 3 | 4 | import { DDNotificationClient } from './notification'; 5 | 6 | let client: MockClient; 7 | let notificationClient: DDNotificationClient; 8 | 9 | beforeEach(() => { 10 | client = new MockClient(); 11 | notificationClient = new DDNotificationClient(client as any); 12 | }); 13 | 14 | describe('notification.send()', () => { 15 | test('sends an open request with definition to parent', async () => { 16 | client.framePostClient.init({ 17 | ...mockContext, 18 | app: { 19 | ...mockContext.app, 20 | features: [FeatureType.SIDE_PANELS] 21 | } 22 | }); 23 | const requestMock = jest 24 | .spyOn(client.framePostClient, 'request') 25 | .mockImplementation(() => null); 26 | 27 | await notificationClient.send({ 28 | label: 'You screwed up bad!', 29 | level: 'danger' 30 | }); 31 | 32 | expect(requestMock).toHaveBeenCalledWith( 33 | RequestType.SEND_NOTIFICATION, 34 | { 35 | label: 'You screwed up bad!', 36 | level: 'danger' 37 | } 38 | ); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/notification/notification.ts: -------------------------------------------------------------------------------- 1 | import { RequestType } from '../constants'; 2 | import { RequestClient, NotificationDefinition } from '../types'; 3 | 4 | export class DDNotificationClient { 5 | private readonly client: RequestClient; 6 | 7 | constructor(client: RequestClient) { 8 | this.client = client; 9 | } 10 | 11 | async send(definition: NotificationDefinition) { 12 | return this.client.request( 13 | RequestType.SEND_NOTIFICATION, 14 | definition 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/side-panel/side-panel.ts: -------------------------------------------------------------------------------- 1 | import { FeatureType, RequestType } from '../constants'; 2 | import { DDFeatureClient } from '../shared/feature-client'; 3 | import { 4 | ContextClient, 5 | LoggerClient, 6 | RequestClient, 7 | SidePanelDefinition 8 | } from '../types'; 9 | import { validateKey } from '../utils/utils'; 10 | 11 | export class DDSidePanelClient extends DDFeatureClient { 12 | constructor(client: ContextClient & LoggerClient & RequestClient) { 13 | super(client, FeatureType.SIDE_PANELS); 14 | } 15 | 16 | /** 17 | * Opens a side panel, given a full side panel definition or the key of a side panel 18 | * definition pre-defined in the app manifest 19 | */ 20 | async open(definition: SidePanelDefinition, args?: unknown) { 21 | if (validateKey(definition)) { 22 | return this.sendRequest(RequestType.OPEN_SIDE_PANEL, { 23 | definition, 24 | args 25 | }); 26 | } 27 | } 28 | 29 | /** 30 | * Closes any active side panels opened by this app. If a key is provided, it will only close the side panel 31 | * if it matches the provided key. 32 | */ 33 | async close(key?: string) { 34 | return this.sendRequest(RequestType.CLOSE_SIDE_PANEL, key); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { DebugClient } from '../types'; 3 | 4 | export class Logger { 5 | private readonly client: DebugClient; 6 | 7 | constructor(client: DebugClient) { 8 | this.client = client; 9 | } 10 | 11 | private getPrefix(): string { 12 | return `dd-apps@${window.location.href}: `; // eslint-disable-line 13 | } 14 | 15 | // TODO: would be nice to prefix with some info about the app 16 | log(message: string) { 17 | if (this.client.debug) { 18 | return console.log(`${this.getPrefix()}${message}`); 19 | } 20 | } 21 | 22 | warn(message: string) { 23 | if (this.client.debug) { 24 | return console.warn(`${this.getPrefix()}${message}`); 25 | } 26 | } 27 | 28 | error(message: string) { 29 | if (this.client.debug) { 30 | return console.error(`${this.getPrefix()}${message}`); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "target": "es5" 8 | }, 9 | "exclude": ["dist"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ui-extensions-sdk/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 4 | const packageJson = require('./package.json'); 5 | 6 | module.exports = (env, options) => { 7 | const config = { 8 | devtool: 'source-map', 9 | target: ['web', 'es5'], 10 | entry: './src/index.ts', 11 | output: { 12 | filename: 'ui-extensions-sdk.min.js', 13 | path: path.resolve(__dirname, 'dist'), 14 | library: 'DD_SDK', 15 | libraryTarget: 'umd' 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.tsx?$/, 21 | loader: 'ts-loader' 22 | } 23 | ] 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | SDK_VERSION: JSON.stringify(packageJson.version) 28 | }) 29 | ], 30 | resolve: { 31 | extensions: ['.ts', '.tsx', '.js'] 32 | }, 33 | devServer: { 34 | clientLogLevel: 'warning', 35 | open: true, 36 | historyApiFallback: true, 37 | stats: 'errors-only' 38 | } 39 | }; 40 | 41 | if (options.mode === 'production') { 42 | config.plugins.push(new CleanWebpackPlugin()); 43 | } 44 | 45 | return config; 46 | }; 47 | -------------------------------------------------------------------------------- /patches/@changesets+assemble-release-plan+5.0.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@changesets/assemble-release-plan/dist/assemble-release-plan.cjs.dev.js b/node_modules/@changesets/assemble-release-plan/dist/assemble-release-plan.cjs.dev.js 2 | index 20a16c8..cf83233 100644 3 | --- a/node_modules/@changesets/assemble-release-plan/dist/assemble-release-plan.cjs.dev.js 4 | +++ b/node_modules/@changesets/assemble-release-plan/dist/assemble-release-plan.cjs.dev.js 5 | @@ -243,6 +243,11 @@ function shouldBumpMajor({ 6 | preInfo, 7 | onlyUpdatePeerDependentsWhenOutOfRange 8 | }) { 9 | + // Don't force a major bump if the dependent is a peer deependency and it's an exact version. 10 | + if (depType === "peerDependencies" && versionRange === nextRelease.oldVersion) { 11 | + return false; 12 | + } 13 | + 14 | // we check if it is a peerDependency because if it is, our dependent bump type might need to be major. 15 | return depType === "peerDependencies" && nextRelease.type !== "none" && nextRelease.type !== "patch" && ( // 1. If onlyUpdatePeerDependentsWhenOutOfRange set to true, bump major if the version is leaving the range. 16 | // 2. If onlyUpdatePeerDependentsWhenOutOfRange set to false, bump major regardless whether or not the version is leaving the range. 17 | -------------------------------------------------------------------------------- /patches/README.md: -------------------------------------------------------------------------------- 1 | # `npm` patches 2 | 3 | This directory holds patches for `npm` dependencies. 4 | 5 | ## `@changesets/assemble-release-plan` 6 | 7 | This patch makes it so peer dependencies that are exact don't force a major bump in `changesets`. 8 | We should still be able to make major bumps, 9 | but they shouldn't happen due to exact peer dependencies. 10 | 11 | This only really works for packages that are "linked." 12 | We should probably add a check for that in the same logic, 13 | but we should really raise the issue upstream and see if there's a fix for it. 14 | 15 | It's not clear whether or not this will go away when ["fixed" packages][] are implemented. 16 | 17 | ["fixed" packages]: https://github.com/changesets/changesets/pull/690 18 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 4, 4 | singleQuote: true, 5 | arrowParens: 'avoid', 6 | trailingComma: 'none' 7 | }; 8 | -------------------------------------------------------------------------------- /scripts/check-example-packages-private.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const packageInfo = require('./package-info'); 3 | 4 | /** 5 | * Checks each example package is private. 6 | * 7 | * We don't want to accidentally publish one of these packages. 8 | */ 9 | function main() { 10 | const errors = []; 11 | 12 | for (const info of packageInfo.getExamples()) { 13 | const private = info.packageJSON.private; 14 | if (private == null) { 15 | errors.push( 16 | `${info.filename}: please add a "private" field with a value of true.` 17 | ); 18 | } 19 | 20 | if (private === false) { 21 | errors.push( 22 | `${info.filename}: please make the "private" field true.` 23 | ); 24 | } 25 | } 26 | 27 | packageInfo.handleErrors(errors); 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /scripts/check-example-packages-version.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const packageInfo = require('./package-info'); 3 | 4 | /** 5 | * Checks each example package has a version field. 6 | * 7 | * Though this field isn't technically required for examples, 8 | * some of our tooling expects it to be there and acts strangely if it's not. 9 | */ 10 | function main() { 11 | const errors = []; 12 | 13 | for (const info of packageInfo.getExamples()) { 14 | const version = info.packageJSON.version; 15 | if (version == null) { 16 | errors.push(`${info.filename}: please add a "version" field.`); 17 | } 18 | } 19 | 20 | packageInfo.handleErrors(errors); 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /scripts/husky-avoid-prepare-in-ci.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | try { 4 | // eslint-disable-next-line global-require, import/no-unresolved 5 | require('husky').install(); 6 | } catch (e) { 7 | if (e.code !== 'MODULE_NOT_FOUND') { 8 | throw e; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "alwaysStrict": true, 5 | "esModuleInterop": true, 6 | "experimentalDecorators": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "strictFunctionTypes": true, 18 | "strictNullChecks": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /typedoc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entryPoints: ['packages/*'], 3 | entryPointStrategy: 'packages', 4 | excludePrivate: true, 5 | excludeInternal: true, 6 | name: 'Datadog Apps API Documentation', 7 | out: 'docs/API/packages', 8 | readme: 'docs/API/README.md' 9 | }; 10 | --------------------------------------------------------------------------------