Skip to content

Commit

Permalink
Add System Resource instead of $resources url
Browse files Browse the repository at this point in the history
  • Loading branch information
istarkov committed May 9, 2024
1 parent efa5445 commit 221ab3d
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { DataSource, Resource } from "@webstudio-is/sdk";
import {
encodeDataSourceVariable,
isLiteralExpression,
isLocalResource,
sitemapResourceUrl,
} from "@webstudio-is/sdk";
import {
Box,
Expand Down Expand Up @@ -446,10 +446,6 @@ export const ResourceForm = forwardRef<
try {
new URL(evaluatedValue);
} catch {
if (isLocalResource(evaluatedValue, "sitemap.xml")) {
return;
}

return "URL is invalid";
}
},
Expand Down Expand Up @@ -630,3 +626,96 @@ export const ResourceForm = forwardRef<
);
});
ResourceForm.displayName = "ResourceForm";

export const SystemResourceForm = forwardRef<
undefined | PanelApi,
{ variable?: DataSource; nameField: Field<string> }
>(({ variable, nameField }, ref) => {
const resources = useStore($resources);

const resource =
variable?.type === "resource"
? resources.get(variable.resourceId)
: undefined;

const method = "get";

const localResources = [
{
label: "Sitemap",
value: JSON.stringify(sitemapResourceUrl()),
description: "Resource that loads the sitemap data of the current site.",
},
];

const [localResource, setLocalResource] = useState(() => {
return (
localResources.find(
(localResource) => localResource.value === resource?.url
) ?? localResources[0]
);
});

const form = composeFields(nameField);

useImperativeHandle(ref, () => ({
...form,
save: () => {
const instanceSelector = $selectedInstanceSelector.get();
if (instanceSelector === undefined) {
return;
}
const [instanceId] = instanceSelector;

const newResource: Resource = {
id: resource?.id ?? nanoid(),
name: nameField.value,
url: localResource.value,
method,
headers: [],
};

const newVariable: DataSource = {
id: variable?.id ?? nanoid(),
// preserve existing instance scope when edit
scopeInstanceId: variable?.scopeInstanceId ?? instanceId,
name: nameField.value,
type: "resource",
resourceId: newResource.id,
};

serverSyncStore.createTransaction(
[$dataSources, $resources],
(dataSources, resources) => {
dataSources.set(newVariable.id, newVariable);
resources.set(newResource.id, newResource);
}
);
},
}));

const resourceId = useId();

return (
<>
<Flex direction="column" css={{ gap: theme.spacing[3] }}>
<Label htmlFor={resourceId}>Resource</Label>
<Select
options={localResources}
getLabel={(option) => option.label}
getValue={(option) => option.value}
getDescription={(option) => {
return (
<Box css={{ width: theme.spacing[25] }}>
{option?.description}
</Box>
);
}}
value={localResource}
onChange={setLocalResource}
/>
</Flex>
</>
);
});
SystemResourceForm.displayName = "ResourceForm";
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
Tooltip,
theme,
} from "@webstudio-is/design-system";
import { transpileExpression } from "@webstudio-is/sdk";
import { isLocalResource, transpileExpression } from "@webstudio-is/sdk";
import type { DataSource } from "@webstudio-is/sdk";
import {
ExpressionEditor,
Expand Down Expand Up @@ -62,7 +62,7 @@ import {
EditorDialogButton,
EditorDialogControl,
} from "~/builder/shared/code-editor-base";
import { ResourceForm } from "./resource-panel";
import { ResourceForm, SystemResourceForm } from "./resource-panel";
import { generateCurl } from "./curl";

/**
Expand Down Expand Up @@ -108,6 +108,7 @@ type VariableType =
| "boolean"
| "json"
| "resource"
| "system-resource"
| "parameter";

type PanelApi = ComposedFields & {
Expand Down Expand Up @@ -359,6 +360,7 @@ const VariablePanel = forwardRef<
}
>(({ variable }, ref) => {
const { allowDynamicData } = useStore($userPlanFeatures);
const resources = useStore($resources);

const nameField = useField({
initialValue: variable?.name ?? "",
Expand All @@ -383,10 +385,18 @@ const VariablePanel = forwardRef<
</Flex>
);

const [type, setType] = useState<VariableType>(() => {
const [variableType, setVariableType] = useState<VariableType>(() => {
if (
variable?.type === "resource" &&
isLocalResource(JSON.parse(resources.get(variable.resourceId)?.url ?? ""))
) {
return "system-resource";
}

if (variable?.type === "parameter" || variable?.type === "resource") {
return variable.type;
}

if (variable?.type === "variable") {
const type = variable.value.type;
if (type === "string" || type === "number" || type === "boolean") {
Expand Down Expand Up @@ -427,7 +437,20 @@ const VariablePanel = forwardRef<
"A Resource is a configuration for secure data fetching. You can safely use secrets in any field.",
},
],
[
"system-resource",
{
label: (
<Flex direction="row" gap="2" align="center">
System Resource
{allowDynamicData === false && <ProBadge>Pro</ProBadge>}
</Flex>
),
description: "A System Resource is a configuration for system data.",
},
],
]);

const typeFieldElement = (
<Flex direction="column" gap="1">
<Label>Type</Label>
Expand All @@ -444,21 +467,21 @@ const VariablePanel = forwardRef<
</Box>
);
}}
value={type}
onChange={setType}
value={variableType}
onChange={setVariableType}
/>
</Flex>
);

if (type === "parameter") {
if (variableType === "parameter") {
return (
<>
{nameFieldElement}
<ParameterForm ref={ref} variable={variable} nameField={nameField} />
</>
);
}
if (type === "string") {
if (variableType === "string") {
return (
<>
{nameFieldElement}
Expand All @@ -467,7 +490,7 @@ const VariablePanel = forwardRef<
</>
);
}
if (type === "number") {
if (variableType === "number") {
return (
<>
{nameFieldElement}
Expand All @@ -476,7 +499,7 @@ const VariablePanel = forwardRef<
</>
);
}
if (type === "boolean") {
if (variableType === "boolean") {
return (
<>
{nameFieldElement}
Expand All @@ -485,7 +508,7 @@ const VariablePanel = forwardRef<
</>
);
}
if (type === "json") {
if (variableType === "json") {
return (
<>
{nameFieldElement}
Expand All @@ -494,7 +517,8 @@ const VariablePanel = forwardRef<
</>
);
}
if (type === "resource") {

if (variableType === "resource") {
return (
<>
{nameFieldElement}
Expand All @@ -503,6 +527,22 @@ const VariablePanel = forwardRef<
</>
);
}

if (variableType === "system-resource") {
return (
<>
{nameFieldElement}
{typeFieldElement}
<SystemResourceForm
ref={ref}
variable={variable}
nameField={nameField}
/>
</>
);
}

variableType satisfies never;
});
VariablePanel.displayName = "VariablePanel";

Expand Down
23 changes: 20 additions & 3 deletions apps/builder/app/routes/rest.resources-loader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from "zod";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json, type ActionFunctionArgs } from "@remix-run/server-runtime";
import {
loadResource,
isLocalResource,
Expand All @@ -21,9 +21,26 @@ export const action = async ({ request }: ActionFunctionArgs) => {
return fetch(input, init);
};

const computedResources = z
const requestJson = await request.json();

const computedResourcesParsed = z
.array(ResourceRequest)
.parse(await request.json());
.safeParse(requestJson);

if (computedResourcesParsed.success === false) {
console.error(
"computedResources.parse",
computedResourcesParsed.error.toString()
);
console.error("data:", requestJson);

throw json(computedResourcesParsed.error, {
status: 400,
});
}

const computedResources = computedResourcesParsed.data;

const responses = await Promise.all(
computedResources.map((resource) => loadResource(customFetch, resource))
);
Expand Down
16 changes: 13 additions & 3 deletions packages/sdk/src/schema/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ const LOCAL_RESOURCE_PREFIX = "$resources";
/**
* Prevents fetch cycles by prefixing local resources.
*/
export const isLocalResource = (pathname: string, resourceName: string) =>
pathname.split("/").filter(Boolean).join("/") ===
`${LOCAL_RESOURCE_PREFIX}/${resourceName}`;
export const isLocalResource = (pathname: string, resourceName?: string) => {
const segments = pathname.split("/").filter(Boolean);

if (resourceName === undefined) {
return segments[0] === LOCAL_RESOURCE_PREFIX;
}

return segments.join("/") === `${LOCAL_RESOURCE_PREFIX}/${resourceName}`;
};

export const sitemapResourceUrl = () => {
return `/${LOCAL_RESOURCE_PREFIX}/sitemap.xml`;
};

0 comments on commit 221ab3d

Please sign in to comment.