Skip to content

Commit

Permalink
feat: Add System Resource instead of $resources url (#3341)
Browse files Browse the repository at this point in the history
## Description

ref #3308

<img width="274" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/dd7ae367-76d8-40cc-9eec-d795aaab54eb">

<img width="286" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/6f57142d-ad6a-43ef-92ba-dff4afce38bf">


# TODO:

- [x] - Remove copy for curl
<img width="344" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/bdd6b92b-5436-4537-b724-a5ecca08e7ca">


## Steps for reproduction

Add Variable, System Resource, open Select, check texts

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env.example`
and the `builder/env-check.js` if mandatory

---------

Co-authored-by: istarkov <[email protected]>
  • Loading branch information
kof and istarkov authored May 11, 2024
1 parent cfe222c commit 2496f68
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 47 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";
108 changes: 77 additions & 31 deletions apps/builder/app/builder/features/settings-panel/variable-popover.tsx
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 Webstudio 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 All @@ -522,6 +562,8 @@ export const VariablePopoverTrigger = forwardRef<
const [triggerRef, sideOffsset] = useSideOffset({ isOpen, containerRef });
const bindingPopoverContainerRef = useRef<HTMLDivElement>(null);
const panelRef = useRef<undefined | PanelApi>();
const resources = useStore($resources);

const saveAndClose = () => {
if (panelRef.current) {
if (panelRef.current.allErrorsVisible === false) {
Expand Down Expand Up @@ -597,28 +639,32 @@ export const VariablePopoverTrigger = forwardRef<
) : (
<FloatingPanelPopoverTitle
actions={
variable.type === "resource" && (
variable?.type === "resource" && (
<>
<Tooltip
content="Copy resource as cURL command"
side="bottom"
>
<Button
aria-label="Copy resource as cURL command"
prefix={<CopyIcon />}
color="ghost"
onClick={() => {
const resourceRequest = getComputedResource(
variable.resourceId
);
if (resourceRequest) {
navigator.clipboard.writeText(
generateCurl(resourceRequest)
{isLocalResource(
JSON.parse(resources.get(variable.resourceId)?.url ?? "")
) === false && (
<Tooltip
content="Copy resource as cURL command"
side="bottom"
>
<Button
aria-label="Copy resource as cURL command"
prefix={<CopyIcon />}
color="ghost"
onClick={() => {
const resourceRequest = getComputedResource(
variable.resourceId
);
}
}}
/>
</Tooltip>
if (resourceRequest) {
navigator.clipboard.writeText(
generateCurl(resourceRequest)
);
}
}}
/>
</Tooltip>
)}
<Tooltip content="Refresh resource data" side="bottom">
<Button
aria-label="Refresh resource data"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => {

// Only xml category is allowed for xml document type
if (documentType === "xml") {
return category === "xml";
return category === "xml" || category === "data";
}
// Hide xml category for non-xml document types
if (category === "xml") {
Expand All @@ -81,8 +81,16 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => {
wrap="wrap"
css={{ px: theme.spacing[9], overflow: "auto" }}
>
{(metaByCategory.get(category) ?? []).map(
(meta: WsComponentMeta, index) => {
{(metaByCategory.get(category) ?? [])
.filter((meta: WsComponentMeta) => {
if (documentType === "xml" && meta.category === "data") {
return (
componentNamesByMeta.get(meta) === "ws:collection"
);
}
return true;
})
.map((meta: WsComponentMeta, index) => {
const component = componentNamesByMeta.get(meta);
if (component === undefined) {
return;
Expand Down Expand Up @@ -123,8 +131,7 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => {
/>
</ListItem>
);
}
)}
})}
{dragCard}
</Flex>
</List>
Expand Down
Loading

0 comments on commit 2496f68

Please sign in to comment.