diff --git a/example-embedded-app/package-lock.json b/example-embedded-app/package-lock.json
index b674697..6b698c3 100644
--- a/example-embedded-app/package-lock.json
+++ b/example-embedded-app/package-lock.json
@@ -23,8 +23,10 @@
"context-api": "0.0.2",
"date-fns": "2.30.0",
"jsonwebtoken": "^9.0.0",
+ "ka-table": "^11.0.3",
"next": "12.3.4",
"nprogress": "0.2.0",
+ "papaparse": "^5.4.1",
"react": "17.0.2",
"react-apexcharts": "1.4.0",
"react-custom-scrollbars-2": "4.5.0",
@@ -4260,6 +4262,14 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/ka-table": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/ka-table/-/ka-table-11.0.3.tgz",
+ "integrity": "sha512-nrauBKNsD3b5uQEQEagFNvAWieBSSXgGJNwW+WO/foaEpGd5RiIB+Si2hoBAV4uB6lhBxANnU59wydyoYi7ALQ==",
+ "peerDependencies": {
+ "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0-0"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -5267,6 +5277,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/papaparse": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
+ "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
diff --git a/example-embedded-app/package.json b/example-embedded-app/package.json
index 89d9ccb..9df9d93 100644
--- a/example-embedded-app/package.json
+++ b/example-embedded-app/package.json
@@ -23,8 +23,10 @@
"context-api": "0.0.2",
"date-fns": "2.30.0",
"jsonwebtoken": "^9.0.0",
+ "ka-table": "^11.0.3",
"next": "12.3.4",
"nprogress": "0.2.0",
+ "papaparse": "^5.4.1",
"react": "17.0.2",
"react-apexcharts": "1.4.0",
"react-custom-scrollbars-2": "4.5.0",
diff --git a/example-embedded-app/pages/csv-example/CsvDataTable.tsx b/example-embedded-app/pages/csv-example/CsvDataTable.tsx
new file mode 100644
index 0000000..c7a0295
--- /dev/null
+++ b/example-embedded-app/pages/csv-example/CsvDataTable.tsx
@@ -0,0 +1,30 @@
+import { Table } from "ka-table";
+import { DataType, EditingMode, SortingMode } from "ka-table/enums";
+import "ka-table/style.css";
+
+function CsvDataTable({ data, table }) {
+ if (data === null || data.length === 0) {
+ return null;
+ }
+
+ const columns = Object.keys(data[0]).map((key) => ({
+ key,
+ title: key,
+ dataType: DataType.String,
+ }));
+
+ const rows = data.map((row, index) => ({ ...row, id: index }));
+
+ return (
+
+ );
+}
+
+export default CsvDataTable;
diff --git a/example-embedded-app/pages/csv-example/PrismaticAvatar.tsx b/example-embedded-app/pages/csv-example/PrismaticAvatar.tsx
new file mode 100644
index 0000000..31c2916
--- /dev/null
+++ b/example-embedded-app/pages/csv-example/PrismaticAvatar.tsx
@@ -0,0 +1,39 @@
+import { CableTwoTone } from "@mui/icons-material";
+import { Avatar } from "@mui/material";
+import config from "prismatic/config";
+import React from "react";
+
+function PrismaticAvatar({ avatarUrl, token }) {
+ const [src, setSrc] = React.useState("");
+
+ React.useEffect(() => {
+ let mounted = true;
+ if (avatarUrl) {
+ fetch(`${config.prismaticUrl}${avatarUrl}`, {
+ headers: { Authorization: `Bearer ${token}` },
+ }).then((response) => {
+ response.json().then((data) => {
+ if (mounted) {
+ setSrc(data.url);
+ }
+ });
+ });
+ }
+
+ return () => {
+ mounted = false;
+ };
+ }, []);
+
+ if (!avatarUrl) {
+ return (
+
+
+
+ );
+ }
+
+ return src ? : null;
+}
+
+export default PrismaticAvatar;
diff --git a/example-embedded-app/pages/csv-example/UploadButtons.tsx b/example-embedded-app/pages/csv-example/UploadButtons.tsx
new file mode 100644
index 0000000..7575ca6
--- /dev/null
+++ b/example-embedded-app/pages/csv-example/UploadButtons.tsx
@@ -0,0 +1,99 @@
+import {
+ Alert,
+ Box,
+ Button,
+ Container,
+ LinearProgress,
+ TextField,
+} from "@mui/material";
+import Papa from "papaparse";
+import React, { Dispatch, SetStateAction } from "react";
+import { Instance } from "./getInstances";
+
+interface UploadCsvParams {
+ fileName: string;
+ uploadUrl: string;
+ data: unknown[];
+ setUploadState: Dispatch>;
+}
+
+function uploadCsv({
+ fileName,
+ uploadUrl,
+ data,
+ setUploadState,
+}: UploadCsvParams) {
+ setUploadState("uploading");
+ const csvData = Papa.unparse(data.map(({ id, ...rest }) => rest));
+ const formData = new FormData();
+ formData.append("file", csvData);
+ formData.append("fileName", fileName);
+ fetch(uploadUrl, { method: "post", body: formData })
+ .then(() => {
+ setUploadState("success");
+ setTimeout(() => setUploadState("idle"), 4000);
+ })
+ .catch(() => {
+ setUploadState("failed");
+ });
+}
+
+type UploadState = "idle" | "uploading" | "success" | "failed";
+
+function ProgressIndicator({ state }: { state: UploadState }) {
+ switch (state) {
+ case "idle":
+ return null;
+ case "uploading":
+ return (
+
+
+
+ );
+ case "success":
+ return Upload successful;
+ case "failed":
+ return Upload failed;
+ }
+}
+
+function UploadButtons({
+ data,
+ instances,
+}: {
+ data: any;
+ instances: Instance[];
+}) {
+ const [fileName, setFileName] = React.useState("");
+ const [uploadState, setUploadState] = React.useState("idle");
+
+ return (
+
+
+ {
+ setFileName(target.value);
+ }}
+ label="File Name"
+ />
+ {instances.map((instance) => (
+
+ ))}
+
+
+ );
+}
+
+export default UploadButtons;
diff --git a/example-embedded-app/pages/csv-example/csv-example.md b/example-embedded-app/pages/csv-example/csv-example.md
new file mode 100644
index 0000000..cc42667
--- /dev/null
+++ b/example-embedded-app/pages/csv-example/csv-example.md
@@ -0,0 +1,3 @@
+## CSV Load and Upload Example
+
+This example demonstrates interactivity between an application and a set of Prismatic instances that a customer has deployed.
diff --git a/example-embedded-app/pages/csv-example/getInstances.ts b/example-embedded-app/pages/csv-example/getInstances.ts
new file mode 100644
index 0000000..cfcb786
--- /dev/null
+++ b/example-embedded-app/pages/csv-example/getInstances.ts
@@ -0,0 +1,78 @@
+import prismatic from "@prismatic-io/embedded";
+
+export interface Instance {
+ id: string;
+ enabled: boolean;
+ flowConfigs: FlowConfigs;
+ integration: Integration;
+ webhookUrls: Record;
+}
+
+export interface FlowConfigs {
+ nodes: FlowConfigsNode[];
+}
+
+export interface FlowConfigsNode {
+ flow: Flow;
+ webhookUrl: string;
+}
+
+export interface Flow {
+ name: string;
+}
+
+export interface Integration {
+ id: string;
+ name: string;
+ avatarUrl: null | string;
+ category: Category;
+}
+
+export enum Category {
+ CSVStores = "CSV Stores",
+ Communication = "Communication",
+ Empty = "",
+}
+const query = `query getInstances {
+ instances {
+ nodes {
+ id
+ enabled
+ flowConfigs {
+ nodes {
+ flow {
+ name
+ }
+ webhookUrl
+ }
+ }
+ integration {
+ id
+ name
+ avatarUrl
+ category
+ }
+ }
+ }
+}
+`;
+
+const getInstances = (): Promise => {
+ return prismatic.graphqlRequest({ query }).then((response) => {
+ const csvInstances = (response.data.instances.nodes as Instance[]).filter(
+ (instance) => instance.integration.category === "CSV Stores",
+ );
+ // Make webhook URLs more accessible
+ for (const instance of csvInstances) {
+ instance.webhookUrls = Object.fromEntries(
+ instance.flowConfigs.nodes.map((flowConfig) => [
+ flowConfig.flow.name,
+ flowConfig.webhookUrl,
+ ]),
+ );
+ }
+ return csvInstances;
+ });
+};
+
+export default getInstances;
diff --git a/example-embedded-app/pages/csv-example/index.tsx b/example-embedded-app/pages/csv-example/index.tsx
new file mode 100644
index 0000000..e49fea1
--- /dev/null
+++ b/example-embedded-app/pages/csv-example/index.tsx
@@ -0,0 +1,163 @@
+import Head from "next/head";
+
+import Footer from "@/components/Footer";
+import SidebarLayout from "@/layouts/SidebarLayout";
+import usePrismaticAuth from "@/usePrismaticAuth";
+import {
+ Button,
+ CardHeader,
+ Container,
+ LinearProgress,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+} from "@mui/material";
+import Papa from "papaparse";
+import React from "react";
+import getInstances, { Instance } from "./getInstances";
+import PrismaticAvatar from "./PrismaticAvatar";
+import CsvDataTable from "./CsvDataTable";
+import { useTable } from "ka-table";
+import UploadButtons from "./UploadButtons";
+import PageTitleWrapper from "@/components/PageTitleWrapper";
+
+import csvExampleHelperText from "./csv-example.md";
+import ExampleHeader from "@/components/ExampleHeader";
+
+function CsvExample() {
+ const { authenticated, token } = usePrismaticAuth();
+ const [instances, setInstances] = React.useState();
+ const [csvFiles, dispatchCsvFiles] = React.useReducer((state, files) => {
+ return [...state, ...files];
+ }, []);
+ const [csvData, setCsvData] = React.useState([]);
+ const [showingTable, setShowingTable] = React.useState(false);
+ const csvDataTable = useTable({
+ onDispatch: (_, tableState) => setCsvData(tableState.data),
+ });
+
+ // Get enabled relevant instances
+ React.useEffect(() => {
+ if (authenticated) {
+ getInstances().then((r) => setInstances(r));
+ }
+ }, [authenticated]);
+
+ // Display CSV files from each integration:
+ React.useEffect(() => {
+ instances?.forEach((instance) => {
+ fetch(instance.webhookUrls["list"]).then((response) => {
+ response.json().then((data) => {
+ dispatchCsvFiles(
+ data
+ .filter(
+ (filename) =>
+ !csvFiles.find(
+ (csvFile) =>
+ csvFile.filename === filename &&
+ csvFile.integrationName === instance.integration.name,
+ ),
+ )
+ .map((filename) => ({
+ filename,
+ downloadFlowUrl: instance.webhookUrls["download"],
+ avatarUrl: instance.integration.avatarUrl,
+ integrationName: instance.integration.name,
+ })),
+ );
+ });
+ });
+ });
+ }, [instances]);
+
+ const loadFile = (filename, downloadUrl) => {
+ fetch(downloadUrl, {
+ method: "post",
+ body: JSON.stringify({ filename }),
+ headers: { "content-type": "application/json" },
+ }).then((response) => {
+ response
+ .text()
+ .then((csvText) =>
+ setCsvData(
+ Papa.parse(csvText.replace(/\n$/, ""), { header: true }).data,
+ ),
+ );
+ setShowingTable(true);
+ });
+ };
+
+ return (
+ <>
+
+ CSV Editor Example
+
+
+
+
+
+
+ {csvFiles.length > 0 ? (
+
+
+
+
+
+ Location
+ File
+
+
+
+ {csvFiles.map((csvFile) => (
+
+
+
+
+
+
+ }
+ title={csvFile.integrationName}
+ />
+
+ {csvFile.filename}
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+ {showingTable ? (
+
+
+
+
+ ) : null}
+
+
+ >
+ );
+}
+
+CsvExample.getLayout = (page) => {page};
+
+export default CsvExample;
diff --git a/example-embedded-app/pages/examples/embedded-marketplace.tsx b/example-embedded-app/pages/examples/embedded-marketplace.tsx
index cea29b7..1048fbd 100644
--- a/example-embedded-app/pages/examples/embedded-marketplace.tsx
+++ b/example-embedded-app/pages/examples/embedded-marketplace.tsx
@@ -27,6 +27,11 @@ function EmbeddedMarketplace() {
prismatic.showMarketplace({
selector: `#${embeddedDivId}`,
theme: "LIGHT",
+ filters: {
+ marketplace: {
+ category: "CSV Stores",
+ },
+ },
});
}
}, [authenticated]);