Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import { ComboBox, IComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import moment from 'moment';
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
import debounce from 'lodash/debounce';

import { FileTypes, nameRegex } from '../../constants';
import { StorageFolder, File } from '../../recoilModel/types';
import { getFileIconName, calculateTimeDiff } from '../../utils/fileUtil';
import httpClient from '../../utils/httpUtil';

// -------------------- Styles -------------------- //

Expand Down Expand Up @@ -173,11 +175,26 @@ export const FileSelector: React.FC<FileSelectorProps> = (props) => {
const [folderName, setFolderName] = useState('');
const [editMode, setEditMode] = useState(EditMode.NONE);
const [nameError, setNameError] = useState('');
const [pathError, setPathError] = useState('');

const validate = debounce(async (path) => {
const response = await httpClient.get(`/storages/validate/${encodeURI(path)}`);
setPathError(response.data.errorMsg);
}, 300);

useEffect(() => {
setCurrentPath(initialPath);
}, [focusedStorageFolder]);

//there is a network delay on the path validation. The error may not exist.
//the initialPath is always a valid directory
//so check and clear path error
useEffect(() => {
if (initialPath === currentPath) {
setPathError('');
}
}, [initialPath, currentPath]);

const createOrUpdateFolder = async (index: number) => {
const isValid = nameRegex.test(folderName);
const isDup = storageFiles.some((file) => file.name === folderName) && storageFiles[index].name !== folderName;
Expand Down Expand Up @@ -480,6 +497,23 @@ export const FileSelector: React.FC<FileSelectorProps> = (props) => {
setCurrentPath(value as string);
}
};

const updatePathPending = (option?: IComboBoxOption, index?: number, value?: string) => {
if (!option && value) {
const path = value.replace(/\\/g, '/');
validate(value);
setCurrentPath(path as string);
}
};

const checkPath = () => {
return pathError
? pathError
: operationMode.write && !focusedStorageFolder.writable
? formatMessage('You do not have permission to save bots here')
: '';
};

return (
<Fragment>
<Stack horizontal styles={stackinput} tokens={{ childrenGap: '2rem' }}>
Expand All @@ -489,15 +523,12 @@ export const FileSelector: React.FC<FileSelectorProps> = (props) => {
useComboBoxAsMenuWidth
autoComplete={'on'}
data-testid={'FileSelectorComboBox'}
errorMessage={
operationMode.write && !focusedStorageFolder.writable
? formatMessage('You do not have permission to save bots here')
: ''
}
errorMessage={checkPath()}
label={formatMessage('Location')}
options={breadcrumbItems}
selectedKey={currentPath}
onChange={updatePath}
onPendingValueChanged={updatePathPending}
/>
</StackItem>
{operationMode.write && (
Expand Down
5 changes: 5 additions & 0 deletions Composer/packages/server/src/controllers/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ function updateCurrentPath(req: Request, res: Response) {
res.status(200).json(StorageService.updateCurrentPath(req.body.path, req.body.storageId));
}

function validatePath(req: Request, res: Response) {
res.status(200).json({ errorMsg: StorageService.validatePath(req.params.path) });
}

async function createFolder(req: Request, res: Response) {
const path = req.body.path;
const folderName = req.body.name;
Expand Down Expand Up @@ -70,4 +74,5 @@ export const StorageController = {
updateFolder,
getBlob,
updateCurrentPath,
validatePath,
};
1 change: 1 addition & 0 deletions Composer/packages/server/src/router/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ router.post('/projects/:projectId/updateBoilerplate', ProjectController.updateBo

// storages
router.put('/storages/currentPath', StorageController.updateCurrentPath);
router.get('/storages/validate/:path', StorageController.validatePath);
router.get('/storages', StorageController.getStorageConnections);
router.post('/storages', StorageController.createStorageConnection);
router.get('/storages/:storageId/blobs', StorageController.getBlob);
Expand Down
12 changes: 11 additions & 1 deletion Composer/packages/server/src/services/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class StorageService {
if (path?.endsWith(':')) {
path = path + '/';
}
if (Path.isAbsolute(path) && fs.existsSync(path)) {
if (Path.isAbsolute(path) && fs.existsSync(path) && fs.statSync(path).isDirectory()) {
const storage = this.storageConnections.find((s) => s.id === storageId);
if (storage) {
storage.path = path;
Expand All @@ -122,6 +122,16 @@ class StorageService {
return this.storageConnections;
};

public validatePath = (path: string) => {
if (!fs.existsSync(path)) {
return 'The path does not exist';
} else if (!fs.statSync(path).isDirectory()) {
return 'This is not a directory';
} else {
return '';
}
};

public createFolder = (path: string) => {
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
Expand Down