From 98822a239045f5c5cbb77935a85d8463a86ec48a Mon Sep 17 00:00:00 2001 From: Cortex Date: Thu, 25 Jan 2024 14:40:06 +0000 Subject: [PATCH] Initial Commit --- plugins/.eslintignore | 2 + plugins/.eslintrc.js | 30 +++++++++++ plugins/.gitignore | 12 +++++ plugins/.prettierignore | 2 + plugins/README.md | 28 ++++++++++ plugins/__mocks__/fileMock.js | 1 + plugins/__mocks__/styleMock.js | 1 + plugins/babel.config.js | 8 +++ plugins/cortex.yaml | 12 +++++ plugins/jest.config.js | 18 +++++++ plugins/package.json | 61 +++++++++++++++++++++ plugins/setupTests.ts | 57 ++++++++++++++++++++ plugins/src/api/Cortex.ts | 7 +++ plugins/src/assets/logo.svg | 3 ++ plugins/src/baseStyles.css | 3 ++ plugins/src/components/App.test.tsx | 10 ++++ plugins/src/components/App.tsx | 26 +++++++++ plugins/src/components/ErrorBoundary.tsx | 39 ++++++++++++++ plugins/src/components/PluginContext.tsx | 15 ++++++ plugins/src/index.html | 10 ++++ plugins/src/index.tsx | 9 ++++ plugins/src/typings.d.ts | 4 ++ plugins/tsconfig.json | 16 ++++++ plugins/webpack.config.js | 69 ++++++++++++++++++++++++ 24 files changed, 443 insertions(+) create mode 100644 plugins/.eslintignore create mode 100644 plugins/.eslintrc.js create mode 100644 plugins/.gitignore create mode 100644 plugins/.prettierignore create mode 100644 plugins/README.md create mode 100644 plugins/__mocks__/fileMock.js create mode 100644 plugins/__mocks__/styleMock.js create mode 100644 plugins/babel.config.js create mode 100644 plugins/cortex.yaml create mode 100644 plugins/jest.config.js create mode 100644 plugins/package.json create mode 100644 plugins/setupTests.ts create mode 100644 plugins/src/api/Cortex.ts create mode 100644 plugins/src/assets/logo.svg create mode 100644 plugins/src/baseStyles.css create mode 100644 plugins/src/components/App.test.tsx create mode 100644 plugins/src/components/App.tsx create mode 100644 plugins/src/components/ErrorBoundary.tsx create mode 100644 plugins/src/components/PluginContext.tsx create mode 100644 plugins/src/index.html create mode 100644 plugins/src/index.tsx create mode 100644 plugins/src/typings.d.ts create mode 100644 plugins/tsconfig.json create mode 100644 plugins/webpack.config.js diff --git a/plugins/.eslintignore b/plugins/.eslintignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/plugins/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/plugins/.eslintrc.js b/plugins/.eslintrc.js new file mode 100644 index 0000000..3a1250c --- /dev/null +++ b/plugins/.eslintrc.js @@ -0,0 +1,30 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "standard-with-typescript", + "prettier", + ], + overrides: [], + parserOptions: { + ecmaVersion: "latest", + project: "tsconfig.json", + sourceType: "module", + tsconfigRootDir: __dirname, + }, + plugins: ["react"], + rules: { + // conflicts with no-extra-boolean-cast + "@typescript-eslint/strict-boolean-expressions": "off", + "no-console": ["error", { allow: ["warn", "error"] }], + }, + settings: { + react: { + version: "detect", + }, + }, +}; diff --git a/plugins/.gitignore b/plugins/.gitignore new file mode 100644 index 0000000..4c577a9 --- /dev/null +++ b/plugins/.gitignore @@ -0,0 +1,12 @@ +# OSX +*.DS_Store + +# IDEs +.idea +*.iml +.vscode + +# This project +node_modules/ +dist/ +yarn-error.log diff --git a/plugins/.prettierignore b/plugins/.prettierignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/plugins/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..abfb954 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,28 @@ +# confluence-plugin + +confluence-plugin is a [Cortex](https://www.cortex.io/) plugin. To see how to run the plugin inside of Cortex, see [our docs](https://docs.cortex.io/docs/plugins). + +### Prerequisites + +Developing and building this plugin requires either [yarn](https://classic.yarnpkg.com/lang/en/docs/install/) or [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). + +## Getting started + +1. Run `yarn` or `npm install` to download all dependencies +2. Run `yarn build` or `npm run build` to compile the plugin code into `./dist/ui.html` +3. Upload `ui.html` into Cortex on a create or edit plugin page +4. Add or update the code and repeat steps 2-3 as necessary + +### Notable scripts + +The following commands come pre-configured in this repository. You can see all available commands in the `scripts` section of [package.json](./package.json). They can be run with npm via `npm run {script_name}` or with yarn via `yarn {script_name}`, depending on your package manager preference. For instance, the `build` command can be run with `npm run build` or `yarn build`. + +- `build` - compiles the plugin. The compiled code root is `./src/index.tsx` (or as defined by [webpack.config.js](webpack.config.js)) and the output is generated into `dist/ui.html`. +- `test` - runs all tests defined in the repository using [jest](https://jestjs.io/) +- `lint` - runs lint and format checking on the repository using [prettier](https://prettier.io/) and [eslint](https://eslint.org/) +- `lintfix` - runs eslint in fix mode to fix any linting errors that can be fixed automatically +- `formatfix` - runs Prettier in fix mode to fix any formatting errors that can be fixed automatically + +### Available React components + +See available UI components via our [Storybook](https://cortexapps.github.io/plugin-core/). diff --git a/plugins/__mocks__/fileMock.js b/plugins/__mocks__/fileMock.js new file mode 100644 index 0000000..0a445d0 --- /dev/null +++ b/plugins/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = "test-file-stub"; diff --git a/plugins/__mocks__/styleMock.js b/plugins/__mocks__/styleMock.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/plugins/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/plugins/babel.config.js b/plugins/babel.config.js new file mode 100644 index 0000000..1442fdf --- /dev/null +++ b/plugins/babel.config.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: ["@babel/plugin-syntax-jsx"], + presets: [ + ["@babel/preset-env", { targets: { node: "current" } }], + "@babel/preset-typescript", + ["@babel/preset-react", { runtime: "automatic" }], + ], +}; diff --git a/plugins/cortex.yaml b/plugins/cortex.yaml new file mode 100644 index 0000000..eab631f --- /dev/null +++ b/plugins/cortex.yaml @@ -0,0 +1,12 @@ +openapi: 3.0.1 +info: + title: confluence-plugin + description: "" + x-cortex-tag: confluence-plugin + x-cortex-git: + github: + repository: cortexapps/cortex-plugins + basepath: plugins + x-cortex-custom-metadata: + cortex-template-version: 0.1.0 + cortex-generated-timestamp: 2024-01-25T14:40:03.811553442 diff --git a/plugins/jest.config.js b/plugins/jest.config.js new file mode 100644 index 0000000..e7fb0cf --- /dev/null +++ b/plugins/jest.config.js @@ -0,0 +1,18 @@ +module.exports = { + moduleNameMapper: { + // map static asset imports to a stub file under the assumption they are not important to our tests + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": + "/__mocks__/fileMock.js", + // map style asset imports to a stub file under the assumption they are not important to our tests + "\\.(css|less)$": "/__mocks__/styleMock.js", + "@cortexapps/plugin-core/components": + "/node_modules/@cortexapps/plugin-core/dist/components.cjs.js", + "@cortexapps/plugin-core": + "/node_modules/@cortexapps/plugin-core/dist/index.cjs.js", + }, + setupFilesAfterEnv: ["/setupTests.ts"], + testEnvironment: "jsdom", + transform: { + "^.+\\.tsx?$": "babel-jest", + }, +}; diff --git a/plugins/package.json b/plugins/package.json new file mode 100644 index 0000000..7f1d576 --- /dev/null +++ b/plugins/package.json @@ -0,0 +1,61 @@ +{ + "name": "confluence-plugin", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@cortexapps/plugin-core": "^2.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@popperjs/core": "^2.11.8", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^14.0.0", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.55.0", + "babel-jest": "^29.5.0", + "css-loader": "^6.7.3", + "eslint": "^8.0.1", + "eslint-config-prettier": "^8.7.0", + "eslint-config-standard-with-typescript": "^34.0.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.6.1", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "html-webpack-plugin": "^5.5.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.8.4", + "prop-types": "^15.8.1", + "react-dev-utils": "^12.0.1", + "style-loader": "^3.3.1", + "terser-webpack-plugin": "^5.3.7", + "ts-loader": "^9.4.2", + "typescript": "^4.9.5", + "url-loader": "^4.1.1", + "webpack": "^5.76.1", + "webpack-cli": "^5.0.1", + "webpack-dev-server": "^4.15.0" + }, + "scripts": { + "build": "webpack --mode=production", + "clean": "rm -r ./dist", + "dev": "webpack serve --mode=development", + "fix": "run-p formatfix lintfix", + "formatfix": "yarn prettier . --write", + "formatcheck": "yarn prettier . --check", + "lint": "run-p formatcheck lintcheck", + "lintcheck": "yarn eslint src", + "lintfix": "yarn lintcheck --fix", + "test": "jest" + } +} diff --git a/plugins/setupTests.ts b/plugins/setupTests.ts new file mode 100644 index 0000000..95cd99a --- /dev/null +++ b/plugins/setupTests.ts @@ -0,0 +1,57 @@ +import "@testing-library/jest-dom/extend-expect"; + +const mockContext = { + apiBaseUrl: "https://api.cortex.dev", + entity: { + definition: null, + description: null, + groups: null, + name: "Inventory planner", + ownership: { + emails: [ + { + description: null, + email: "nikhil@cortex.io", + inheritance: null, + id: 1, + }, + ], + }, + tag: "inventory-planner", + type: "service", + }, + location: "ENTITY", + user: { + email: "ganesh@cortex.io", + name: "Ganesh Datta", + role: "ADMIN", + }, +}; + +jest.mock("@cortexapps/plugin-core/components", () => { + const originalModule = jest.requireActual( + "@cortexapps/plugin-core/components" + ); + return { + ...originalModule, + usePluginContext: () => { + return mockContext; + }, + PluginProvider: ({ children }) => { + return children; + }, + }; +}); + +jest.mock("@cortexapps/plugin-core", () => { + const originalModule = jest.requireActual("@cortexapps/plugin-core"); + return { + ...originalModule, + CortexApi: { + ...originalModule.CortexApi, + getContext: () => { + return mockContext; + }, + }, + }; +}); diff --git a/plugins/src/api/Cortex.ts b/plugins/src/api/Cortex.ts new file mode 100644 index 0000000..aac391c --- /dev/null +++ b/plugins/src/api/Cortex.ts @@ -0,0 +1,7 @@ +import { CortexApi, type CortexContextResponse } from "@cortexapps/plugin-core"; + +export const getCortexContext = async (): Promise => { + const context = await CortexApi.getContext(); + + return context; +}; diff --git a/plugins/src/assets/logo.svg b/plugins/src/assets/logo.svg new file mode 100644 index 0000000..2abafcd --- /dev/null +++ b/plugins/src/assets/logo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/src/baseStyles.css b/plugins/src/baseStyles.css new file mode 100644 index 0000000..e9c5e5f --- /dev/null +++ b/plugins/src/baseStyles.css @@ -0,0 +1,3 @@ +body { + font: 14px sans-serif; +} diff --git a/plugins/src/components/App.test.tsx b/plugins/src/components/App.test.tsx new file mode 100644 index 0000000..625761e --- /dev/null +++ b/plugins/src/components/App.test.tsx @@ -0,0 +1,10 @@ +import { render, screen } from "@testing-library/react"; +import App from "./App"; + +describe("App", () => { + it("indicates that it's an awesome plugin", () => { + render(); + + expect(screen.queryByText(/My Awesome Cortex Plugin/)).toBeInTheDocument(); + }); +}); diff --git a/plugins/src/components/App.tsx b/plugins/src/components/App.tsx new file mode 100644 index 0000000..cc54cbb --- /dev/null +++ b/plugins/src/components/App.tsx @@ -0,0 +1,26 @@ +import type React from "react"; +import { + Logo, + PluginProvider, + Stack, + Title, +} from "@cortexapps/plugin-core/components"; +import "../baseStyles.css"; +import ErrorBoundary from "./ErrorBoundary"; +import PluginContext from "./PluginContext"; + +const App: React.FC = () => { + return ( + + + + + My Awesome Cortex Plugin + + + + + ); +}; + +export default App; diff --git a/plugins/src/components/ErrorBoundary.tsx b/plugins/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..f862bd3 --- /dev/null +++ b/plugins/src/components/ErrorBoundary.tsx @@ -0,0 +1,39 @@ +import React from "react"; + +interface ErrorBoundaryProps extends React.PropsWithChildren {} + +interface ErrorBoundaryState { + hasError: boolean; +} + +class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + public state: ErrorBoundaryState = { + hasError: false, + }; + + public static getDerivedStateFromError(_: Error): ErrorBoundaryState { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + console.error("Uncaught error:", error, errorInfo); + } + + public render(): React.ReactNode { + if (this.state.hasError) { + return ( +

+ Oops! There was a runtime error. See the console for more details. +

+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/plugins/src/components/PluginContext.tsx b/plugins/src/components/PluginContext.tsx new file mode 100644 index 0000000..13ee6d4 --- /dev/null +++ b/plugins/src/components/PluginContext.tsx @@ -0,0 +1,15 @@ +import { Title, usePluginContext } from "@cortexapps/plugin-core/components"; +import type React from "react"; + +const PluginContext: React.FC = () => { + const context = usePluginContext(); + + return ( + <> + Plugin context +
{JSON.stringify(context, null, 2)}
+ + ); +}; + +export default PluginContext; diff --git a/plugins/src/index.html b/plugins/src/index.html new file mode 100644 index 0000000..a599c3a --- /dev/null +++ b/plugins/src/index.html @@ -0,0 +1,10 @@ + + + + +
+ diff --git a/plugins/src/index.tsx b/plugins/src/index.tsx new file mode 100644 index 0000000..2a10097 --- /dev/null +++ b/plugins/src/index.tsx @@ -0,0 +1,9 @@ +import { createRoot } from "react-dom/client"; +import App from "./components/App"; + +document.addEventListener("DOMContentLoaded", function () { + const container = document.getElementById("cortex-plugin-root"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const root = createRoot(container!); + root.render(); +}); diff --git a/plugins/src/typings.d.ts b/plugins/src/typings.d.ts new file mode 100644 index 0000000..1a3dd3c --- /dev/null +++ b/plugins/src/typings.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + const content: any; + export default content; +} diff --git a/plugins/tsconfig.json b/plugins/tsconfig.json new file mode 100644 index 0000000..da7c975 --- /dev/null +++ b/plugins/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "noImplicitAny": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist", + "removeComments": true, + "strictNullChecks": true, + "target": "es6", + "typeRoots": ["./node_modules/@types"] + } +} diff --git a/plugins/webpack.config.js b/plugins/webpack.config.js new file mode 100644 index 0000000..851c75f --- /dev/null +++ b/plugins/webpack.config.js @@ -0,0 +1,69 @@ +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin"); +const TerserPlugin = require("terser-webpack-plugin"); +const path = require("path"); + +module.exports = (env, argv) => ({ + mode: argv.mode === "production" ? "production" : "development", + + entry: { + ui: "./src/index.tsx", // The entry point for your UI plugin + }, + + module: { + rules: [ + // Converts TypeScript code to JavaScript + { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ }, + + // Enables including CSS by doing "import './file.css'" in your TypeScript code + { test: /\.css$/, use: ["style-loader", { loader: "css-loader" }] }, + + // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI + { test: /\.(png|jpg|gif|webp|svg)$/, loader: "url-loader" }, + ], + }, + + // minify the code + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + output: { + // make sure emojis don't get mangled 🙂 + ascii_only: true, + }, + }, + }), + ], + usedExports: true, + }, + + // Webpack tries these extensions for you if you omit the extension, like "import './file'" + resolve: { extensions: [".tsx", ".ts", ".jsx", ".js"] }, + + output: { + filename: "[name].js", + path: path.resolve(__dirname, "dist"), // Compile into a folder named "dist" + publicPath: "", + }, + + // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it + plugins: [ + new HtmlWebpackPlugin({ + template: "./src/index.html", + filename: "ui.html", + chunks: ["ui"], + cache: false, + }), + new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/ui/]), + ], + + devServer: { + compress: true, + port: 9000, + static: { + directory: path.join(__dirname, "dist"), + }, + }, +});