Skip to content

Info cards plugin #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/info-cards/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
30 changes: 30 additions & 0 deletions plugins/info-cards/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -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",
},
},
};
12 changes: 12 additions & 0 deletions plugins/info-cards/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# OSX
*.DS_Store

# IDEs
.idea
*.iml
.vscode

# This project
node_modules/
dist/
yarn-error.log
2 changes: 2 additions & 0 deletions plugins/info-cards/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
28 changes: 28 additions & 0 deletions plugins/info-cards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Example

Info Cards 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/).
1 change: 1 addition & 0 deletions plugins/info-cards/__mocks__/fileMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = "test-file-stub";
1 change: 1 addition & 0 deletions plugins/info-cards/__mocks__/styleMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
8 changes: 8 additions & 0 deletions plugins/info-cards/babel.config.js
Original file line number Diff line number Diff line change
@@ -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" }],
],
};
21 changes: 21 additions & 0 deletions plugins/info-cards/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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)$":
"<rootDir>/__mocks__/fileMock.js",
// map style asset imports to a stub file under the assumption they are not important to our tests
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js",
"@cortexapps/plugin-core/components":
"<rootDir>/../../node_modules/@cortexapps/plugin-core/dist/components.cjs.js",
"@cortexapps/plugin-core":
"<rootDir>/../../node_modules/@cortexapps/plugin-core/dist/index.cjs.js",
},
setupFilesAfterEnv: ["<rootDir>/setupTests.ts"],
testEnvironment: "jsdom",
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
},
transformIgnorePatterns: [
"/node_modules/(?!yaml)", // yaml is commonjs evidently
],
};
73 changes: 73 additions & 0 deletions plugins/info-cards/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"name": "info-cards",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@codemirror/lang-html": "^6.4.9",
"@cortexapps/plugin-core": "^2.1.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@nikolovlazar/chakra-ui-prose": "^1.2.1",
"@uiw/react-codemirror": "^4.23.7",
"dompurify": "^3.2.3",
"framer-motion": "^11.13.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"yaml": "^2.6.1"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/plugin-syntax-jsx": "^7.18.6",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@popperjs/core": "^2.11.8",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.3",
"@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.7.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.6.1",
"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"
}
}
57 changes: 57 additions & 0 deletions plugins/info-cards/setupTests.ts
Original file line number Diff line number Diff line change
@@ -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: "[email protected]",
inheritance: null,
id: 1,
},
],
},
tag: "inventory-planner",
type: "service",
},
location: "ENTITY",
user: {
email: "[email protected]",
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;
},
},
};
});
7 changes: 7 additions & 0 deletions plugins/info-cards/src/api/Cortex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CortexApi, type CortexContextResponse } from "@cortexapps/plugin-core";

export const getCortexContext = async (): Promise<CortexContextResponse> => {
const context = await CortexApi.getContext();

return context;
};
3 changes: 3 additions & 0 deletions plugins/info-cards/src/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions plugins/info-cards/src/baseStyles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
font: 14px sans-serif;
}
108 changes: 108 additions & 0 deletions plugins/info-cards/src/components/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { render, screen, waitFor } from "@testing-library/react";
import fetchMock from "jest-fetch-mock";
import App from "./App";
import { act } from "react-dom/test-utils";

describe("App", () => {
beforeEach(() => {
fetchMock.enableMocks();
fetchMock.resetMocks();
});

it("shows instructions when it's not configured", async () => {
fetchMock.mockResponse(async (req) => {
if (
req.url ===
"https://api.cortex.dev/catalog/info-cards-plugin-config/openapi"
) {
return {
status: 200,
body: JSON.stringify({
openapi: "3.0.1",
info: {
title: "Info Cards Plugin",
description: "it is an awesome plugin",
"x-cortex-tag": "info-cards-plugin",
"x-cortex-type": "plugin-configuration",
},
}),
};
}
return {
status: 404,
};
});
act(() => {
render(<App />);
});

await waitFor(() => {
expect(
screen.queryByText(
/To get started, please configure the layout and cards in the editor/
)
).toBeInTheDocument();
});
});

it("shows content when it's configured", async () => {
fetchMock.mockResponse(async (req) => {
if (
req.url ===
"https://api.cortex.dev/catalog/info-cards-plugin-config/openapi"
) {
return {
status: 200,
body: JSON.stringify({
openapi: "3.0.1",
info: {
title: "Info Cards Plugin",
description: "it is an awesome plugin",
"x-cortex-tag": "info-cards-plugin",
"x-cortex-type": "plugin-configuration",
"x-cortex-definition": {
infoRows: [
{
id: 1734449412873,
cards: [
{
id: 1734449414053,
rowId: 1734449412873,
title: "meeps",
contentHTML: "<h1>meeps</h1>",
contentType: "HTML",
},
{
id: 1734459065666,
rowId: 1734449412873,
title: "woot",
contentIFrameURL: "https://example.com",
contentType: "IFrameURL",
},
],
},
],
},
},
}),
};
}
return {
status: 404,
};
});

act(() => {
render(<App />);
});

await waitFor(() => {
const heading = screen.getByRole("heading", { level: 1, name: /meeps/i });
expect(heading).toBeInTheDocument();

const iframe = screen.getByTitle("woot");
expect(iframe).toBeInTheDocument();
expect(iframe).toHaveAttribute("src", "https://example.com");
});
});
});
25 changes: 25 additions & 0 deletions plugins/info-cards/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type React from "react";
import { PluginProvider } from "@cortexapps/plugin-core/components";
import { ChakraProvider } from "@chakra-ui/react";

import "../baseStyles.css";
import ErrorBoundary from "./ErrorBoundary";
import PluginRoot from "./PluginRoot";
import theme from "./ui/theme";

const App: React.FC = () => {
return (
<ErrorBoundary>
<PluginProvider>
<ChakraProvider
theme={theme}
toastOptions={{ defaultOptions: { position: "top" } }}
>
<PluginRoot />
</ChakraProvider>
</PluginProvider>
</ErrorBoundary>
);
};

export default App;
Loading