Skip to content

Commit

Permalink
Improve onboarding experience
Browse files Browse the repository at this point in the history
Unify entities titles and icons drawing across all components
Provisioning DB name - clear unsupported symbols from slug
Remove object name min characters requirement
  • Loading branch information
absorbb committed Jul 11, 2023
1 parent 9761fb8 commit 96961df
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 211 deletions.
20 changes: 15 additions & 5 deletions webapps/console/components/ConfigObjectEditor/ConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { EditorField } from "./EditorField";
import { EditorButtons } from "./EditorButtons";
import { ButtonGroup, ButtonProps } from "../ButtonGroup/ButtonGroup";
import cuid from "cuid";
import { ObjectTitle } from "../ObjectTitle/ObjectTitle";

const log = getLog("ConfigEditor");

Expand All @@ -65,6 +66,7 @@ export type ConfigEditorProps<T extends { id: string } = { id: string }, M = {}>
listTitle?: ReactNode;
type: string;
listColumns?: { title: ReactNode; render: (o: T) => ReactNode }[];
icon?: (o: T) => ReactNode;
name?: (o: T) => string;
objectType: FunctionLike<ZodType<T>, T>;
fields: Record<string, FieldDisplay>;
Expand Down Expand Up @@ -93,6 +95,7 @@ export type ConfigEditorProps<T extends { id: string } = { id: string }, M = {}>
editorComponent?: EditorComponentFactory;
testConnectionEnabled?: (o: any) => boolean;
onTest?: (o: T) => Promise<ConfigTestResult>;
backTo?: string;
};

export type CustomWidgetProps<T> = {
Expand Down Expand Up @@ -347,6 +350,7 @@ const SingleObjectEditor: React.FC<SingleObjectEditorProps> = props => {
newObject = () => ({}),
loadMeta,
onTest,
backTo,
...otherProps
} = props;
const [meta, setMeta] = useState<any>(undefined);
Expand Down Expand Up @@ -402,7 +406,11 @@ const SingleObjectEditor: React.FC<SingleObjectEditorProps> = props => {
await getConfigApi(workspace.id, type).update(object.id, newObject);
//await new Promise(resolve => setTimeout(resolve, 10000000));
}
router.push(`/${workspace.id}/${type}s`);
if (backTo) {
router.push(`/${workspace.id}${backTo}`);
} else {
router.push(`/${workspace.id}/${type}s`);
}
} catch (error) {
feedbackError("Failed to save object", { error });
}
Expand Down Expand Up @@ -591,15 +599,16 @@ const ConfigEditor: React.FC<ConfigEditorProps> = props => {
const router = useRouter();
const id = router.query.id as string;
const clone = router.query.clone as string;
const backTo = router.query.backTo as string;
if (id) {
if (id === "new") {
if (clone) {
return <SingleObjectEditorLoader {...props} id={clone} clone={true} />;
return <SingleObjectEditorLoader {...props} id={clone} backTo={backTo} clone={true} />;
} else {
return <SingleObjectEditor {...props} />;
return <SingleObjectEditor {...props} backTo={backTo} />;
}
} else {
return <SingleObjectEditorLoader {...props} id={id} />;
return <SingleObjectEditorLoader {...props} id={id} backTo={backTo} />;
}
} else {
return <ObjectListEditor {...props} />;
Expand All @@ -617,6 +626,7 @@ const ObjectsList: React.FC<{ objects: any[]; onDelete: (id: string) => Promise<
listColumns = [],
actions = [],
noun,
icon,
name = (o: any) => o.name,
}) => {
const modal = useAntdModal();
Expand All @@ -635,7 +645,7 @@ const ObjectsList: React.FC<{ objects: any[]; onDelete: (id: string) => Promise<
title: "Name",
render: (text, record) => (
<WLink href={`/${type}s?id=${record.id}`}>
<span className="text-text font-bold">{name(record)}</span>
<ObjectTitle title={name(record)} icon={icon ? icon(record) : undefined} />
</WLink>
),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { DataLayoutType } from "@jitsu/protocols/analytics";
import { ChevronLeft } from "lucide-react";
import styles from "./ConnectionEditorPage.module.css";
import { JitsuButton } from "../JitsuButton/JitsuButton";
import { StreamTitle } from "../../pages/[workspaceId]/streams";
import { DestinationTitle } from "../../pages/[workspaceId]/destinations";

const log = getLog("ConnectionEditorPage");

Expand All @@ -42,11 +44,18 @@ function DestinationSelector(props: SelectorProps<DestinationConfig>) {
const destinationType = getCoreDestinationType(destination.destinationType);
return (
<Select.Option dropdownMatchSelectWidth={false} value={destination.id} key={destination.id}>
<div className="flex items-center">
<div className="w-5 h-5 mr-2 shrink-0">{destinationType.icon}</div>
<div className="whitespace-nowrap">{destination.name}</div>
<div className="text-xxs text-gray-500 ml-1">({destinationType.title})</div>
</div>
<DestinationTitle
destination={destination}
size={"small"}
title={(d, t) => {
return (
<div className={"flex flex-row items-center"}>
<div className="whitespace-nowrap">{destination.name}</div>
<div className="text-xxs text-gray-500 ml-1">({destinationType.title})</div>
</div>
);
}}
/>
</Select.Option>
);
})}
Expand All @@ -70,7 +79,7 @@ function SourceSelector(props: SelectorProps<StreamConfig>) {
<Select dropdownMatchSelectWidth={false} className="w-80" value={props.selected} onSelect={props.onSelect}>
{props.items.map(stream => (
<Select.Option key={stream.id} value={stream.id}>
{stream.name}
<StreamTitle stream={stream} size={"small"} />
</Select.Option>
))}
</Select>
Expand Down Expand Up @@ -541,7 +550,6 @@ function ConnectionEditor({
const functionId = "udf." + func.id;
configItems.push({
group: "Functions",
documentation: func.description,
name: func.name,
link: `/functions?id=${func.id}`,
component: (
Expand Down Expand Up @@ -625,7 +633,11 @@ function ConnectionEditor({
await get(`/api/${workspace.id}/config/link`, {
body: { fromId: srcId, toId: dstId, type: "push", data: connectionOptions },
});
router.back();
if (router.query.backTo) {
router.push(`/${workspace.id}${router.query.backTo}`);
} else {
router.back();
}
} catch (error) {
feedbackError(`Can't link destinations`, { error });
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,17 @@ export const ConnectionsDiagram: React.FC<ConnectionDiagramProps> = ({ connectio
.forEach((r, idx) => {
const rel = getRelativePosition(canvasRef.current!, r!);
const bounds = r!.getBoundingClientRect();
const source = sources[idx];
const selected =
mouseOverSrc === sources[idx].id ||
(!!mouseOverDst && !!connections.find(c => c.from === sources[idx].id && c.to === mouseOverDst));
newLines.push({
from: { top: rel.top + bounds.height / 2, left: rel.left + bounds.width },
to: { left: logoPosition.left, top: logoPosition.top + logoBounds.height / 2 },
selected,
});
mouseOverSrc === source.id ||
(!!mouseOverDst && !!connections.find(c => c.from === source.id && c.to === mouseOverDst));
if (connections.find(c => c.from === source.id)) {
newLines.push({
from: { top: rel.top + bounds.height / 2, left: rel.left + bounds.width },
to: { left: logoPosition.left, top: logoPosition.top + logoBounds.height / 2 },
selected,
});
}
});

dstRefs.current
Expand All @@ -124,11 +127,13 @@ export const ConnectionsDiagram: React.FC<ConnectionDiagramProps> = ({ connectio
setForceSelectDestination([destination.id]);
setForceSelectSource(connections.filter(c => c.to === destination.id).map(c => c.from));
}
newLines.push({
to: { top: rel.top + bounds.height / 2, left: rel.left },
from: { left: logoPosition.left + logoBounds.width, top: logoPosition.top + logoBounds.height / 2 },
selected,
});
if (connections.find(c => c.to === destination.id)) {
newLines.push({
to: { top: rel.top + bounds.height / 2, left: rel.left },
from: { left: logoPosition.left + logoBounds.width, top: logoPosition.top + logoBounds.height / 2 },
selected,
});
}
});
setLines(newLines);

Expand Down
10 changes: 4 additions & 6 deletions webapps/console/components/DataView/EventsBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { FunctionConfig } from "../../lib/schema";
import { arrayToMap } from "../../lib/shared/arrays";
import { RefreshCw } from "lucide-react";
import { JitsuButton } from "../JitsuButton/JitsuButton";
import { ConnectionTitle } from "../../pages/[workspaceId]/connections";
import { StreamTitle } from "../../pages/[workspaceId]/streams";

dayjs.extend(utc);
dayjs.extend(relativeTime);
Expand Down Expand Up @@ -278,13 +280,9 @@ export const EventsBrowser = ({
value: entity[0],
label:
entity[1].type === "stream" ? (
entity[1].name
<StreamTitle stream={entity[1]} size={"small"} />
) : (
<div className={"flex flex-row gap-1.5"}>
<span>{entity[1].stream?.name || "unknown"}</span>
<span>{"→"}</span>
<DestinationTitle size={"small"} destination={entity[1].destination} />
</div>
<ConnectionTitle connectionId={entity[0]} stream={entity[1].stream} destination={entity[1].destination} />
),
}));
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import React, { useMemo } from "react";
import styles from "./DestinationsCatalog.module.css";

import { coreDestinations, DestinationType } from "../../lib/schema/destinations";
Expand Down Expand Up @@ -48,34 +48,38 @@ export function getDestinationIcon(destination?: DestinationType) {
export const DestinationCatalog: React.FC<{ onClick: (destinationType: string) => void }> = ({ onClick }) => {
const groups = useMemo(() => groupDestinationTypes(), []);
return (
<div className="p-12">
{Object.entries(groups).map(([tag, destinations]) => (
<div key={tag} className="">
<div className="text-3xl text-textLight px-4 pb-0 pt-3">{tag}</div>
<div className="flex flex-wrap">
{destinations.map(destination => (
<div
key={destination.id}
className={`cursor-pointer relative w-72 border border-textDisabled ${
!destination.comingSoon && "hover:scale-105 hover:border-primary"
} transition ease-in-out flex rounded-lg px-4 py-4 space-x-4 m-4`}
onClick={() => onClick(destination.id)}
>
{destination.comingSoon && (
<div className="absolute -right-2 -top-2 bg-primary text-backgroundLight px-1 py-0.5 rounded">
Coming soon
<div className="p-12 flex flex-col flex-shrink w-full h-full overflow-y-auto">
<div className={"flex-shrink overflow-scroll"}>
{Object.entries(groups).map(([tag, destinations]) => (
<div key={tag} className="">
<div className="text-3xl text-textLight px-4 pb-0 pt-3">{tag}</div>
<div className="flex flex-wrap">
{destinations.map(destination => (
<div
key={destination.id}
className={`cursor-pointer relative w-72 border border-textDisabled ${
!destination.comingSoon && "hover:scale-105 hover:border-primary"
} transition ease-in-out flex rounded-lg px-4 py-4 space-x-4 m-4`}
onClick={() => onClick(destination.id)}
>
{destination.comingSoon && (
<div className="absolute -right-2 -top-2 bg-primary text-backgroundLight px-1 py-0.5 rounded">
Coming soon
</div>
)}
<div className={styles.icon}>{getDestinationIcon(destination)}</div>
<div>
<div className={`text-xl ${destination.comingSoon && "text-textDisabled"}`}>
{destination.title}
</div>
{destination.description && <div className="text-xs text-textLight">{destination.description}</div>}
</div>
)}
<div className={styles.icon}>{getDestinationIcon(destination)}</div>
<div>
<div className={`text-xl ${destination.comingSoon && "text-textDisabled"}`}>{destination.title}</div>
{destination.description && <div className="text-xs text-textLight">{destination.description}</div>}
</div>
</div>
))}
))}
</div>
</div>
</div>
))}
))}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ const EditorItemTable: React.FC<{ items: Omit<EditorItem, "group">[]; className?
<div className={`${styles.editorItemRow} h-full`} key={getKey(item)}>
{item.name && (
<div className={styles.name}>
<DocumentedLabel name={item.name} doc={item.documentation} />
{item.link && (
<WLink href={item.link} target={"_blank"} rel={"noreferrer noopener"}>
<FaExternalLinkAlt className={"anticon ml-2"} />
</WLink>
)}
<div className={`flex flex-row items-center gap-2`}>
<DocumentedLabel name={item.name} doc={item.documentation} />
{item.link && (
<WLink href={item.link} target={"_blank"} rel={"noreferrer noopener"}>
<FaExternalLinkAlt className={"w-2.5 h-2.5"} />
</WLink>
)}
</div>
</div>
)}
<div className={item.itemClassName ?? styles.component}> {item.component}</div>
Expand Down
24 changes: 24 additions & 0 deletions webapps/console/components/ObjectTitle/ObjectTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";

export const ObjectTitle: React.FC<{
size?: "small" | "default" | "large";
icon?: React.ReactNode;
title: string | React.ReactNode;
}> = ({ icon, title, size = "default" }) => {
const iconClassName = (() => {
switch (size) {
case "small":
return "h-4 w-4";
case "large":
return "h-10 w-10";
default:
return "h-6 w-6";
}
})();
return (
<div className={`flex items-center ${size !== "small" ? "gap-3" : "gap-2"}`}>
{icon && <div className={iconClassName}>{icon}</div>}
<div className={`text-text ${size !== "small" ? "font-semibold" : ""}`}>{title}</div>
</div>
);
};
Loading

0 comments on commit 96961df

Please sign in to comment.