improve discoverability and UX for replicate folders/secrets feature

This commit is contained in:
x032205
2025-10-04 03:40:14 -04:00
parent 57eb8af155
commit 79278b03e0
6 changed files with 119 additions and 60 deletions
+20
View File
@@ -49,3 +49,23 @@ In the image below, you can see that the **Development** environment is the only
in the `/users` folder, being other folders `/user-a`, `/user-b`, ... `/user-f`.
![comparing folders](../../images/platform/folder/folders-secrets-overview.png)
### Replicating Folder Contents
If you want to copy secrets or folders from one path to another, you can utilize the **Replicate Secrets** functionality located in the **Add Secret** dropdown.
![replicate secrets](../../images/platform/folder/replicate-secrets.png)
![replicate secrets modal](../../images/platform/folder/replicate-secrets-modal.png)
First, select the **Source Environment** and the **Source Root Path** you want to copy secrets *from*. In the example provided, we select `/dev-folder` as the source root path from the Development environment. This means any secrets within `/dev-folder` from Development will be replicated. By default, these secrets are copied into the *currently active* folder/path in your target environment (e.g., the root folder of your Staging environment in this scenario).
As a final step, you can select the specific secrets you wish to copy and then click **Replicate Secrets**.
![replicate secrets modal](../../images/platform/folder/replicate-secrets-result.png)
The result shows two secrets successfully copied from the `/dev-folder` in the Development environment into the root folder of the Staging environment.
<Info>
If you do not select a **Source Root Path**, the replication will consider the contents of the *entire root* of the **Source Environment** (e.g., the Development environment). In this example that would mean copying the `/dev-folder` itself rather than just it's contents.
</Info>
Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 KiB

@@ -380,7 +380,7 @@ export const ActionBar = ({
}
};
// Replicate Folder Logic
// Replicate Secrets Logic
const createSecretCount = Object.keys(
(popUp.confirmUpload?.data as TSecOverwriteOpt)?.create || {}
).length;
@@ -439,7 +439,7 @@ export const ActionBar = ({
const processBatches = async () => {
await secretBatches.reduce(async (previous, batch) => {
await previous;
try {
const { secrets: batchSecrets } = await fetchDashboardProjectSecretsByKeys({
secretPath: normalizedPath,
environment,
@@ -450,6 +450,11 @@ export const ActionBar = ({
batchSecrets.forEach((secret) => {
existingSecretLookup.add(`${normalizedPath}-${secret.secretKey}`);
});
} catch (error) {
if (!(error instanceof AxiosError && error.response?.status === 404)) {
throw error;
}
}
}, Promise.resolve());
};
@@ -1050,7 +1055,7 @@ export const ActionBar = ({
className="h-10 text-left"
isFullWidth
>
Replicate Folder
Replicate Secrets
</Button>
)}
</ProjectPermissionCan>
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { faClone } from "@fortawesome/free-solid-svg-icons";
import { faArrowUpRightFromSquare, faBookOpen, faClone } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@@ -226,8 +226,27 @@ export const ReplicateFolderFromBoard = ({
<ModalContent
bodyClassName="overflow-visible"
className="max-w-2xl"
title="Replicate Folder Content From An Environment"
subTitle="Replicate folder content from other environments into this context"
title={
<div className="flex items-center gap-1 text-mineshaft-300">
<span>Replicate Secrets</span>
<a
target="_blank"
href="https://infisical.com/docs/documentation/platform/folder#replicating-folder-contents"
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mb-[0.03rem] mr-1 text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
</div>
}
subTitle="Copy folder contents from other locations into this context"
>
<form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="flex items-center space-x-2">
@@ -235,7 +254,12 @@ export const ReplicateFolderFromBoard = ({
control={control}
name="environment"
render={({ field: { value, onChange } }) => (
<FormControl label="Environment" isRequired className="w-1/3">
<FormControl
tooltipText="The environment to replicate secrets from"
label="Source Environment"
isRequired
className="w-1/3"
>
<FilterableSelect
value={value}
onChange={onChange}
@@ -251,7 +275,12 @@ export const ReplicateFolderFromBoard = ({
control={control}
name="secretPath"
render={({ field }) => (
<FormControl label="Secret Path" className="flex-grow" isRequired>
<FormControl
tooltipText="The folder to use as a root for replication. Using /foo as a root for /foo/bar will result in /bar being copied to this context."
label="Source Root Path"
className="flex-grow"
isRequired
>
<SecretPathInput
{...field}
placeholder="Provide a path, default is /"
@@ -261,12 +290,16 @@ export const ReplicateFolderFromBoard = ({
)}
/>
</div>
<div className="border-t border-mineshaft-600 pt-4">
<Controller
control={control}
name="secrets"
render={({ field: { onChange } }) => (
<FormControl className="flex-grow" isRequired>
<FormControl
tooltipText="The folders and secrets to replicate into this context"
className="flex-grow"
label="Affected Subjects"
isRequired
>
<SecretTreeView
data={secretsFilteredByPath}
basePath={debouncedEnvCopySecretPath}
@@ -291,15 +324,16 @@ export const ReplicateFolderFromBoard = ({
<Button
leftIcon={<FontAwesomeIcon icon={faClone} />}
type="submit"
variant="outline_bg"
colorSchema="secondary"
isDisabled={!selectedSecrets || selectedSecrets.length === 0}
>
Replicate Folder
Replicate Secrets
</Button>
<Button variant="plain" colorSchema="secondary" onClick={() => onToggle(false)}>
Cancel
</Button>
</div>
</div>
</form>
</ModalContent>
</Modal>