Skip to content

Commit

Permalink
fix: create resource and save to folder / append to file duplicate
Browse files Browse the repository at this point in the history
  • Loading branch information
topliceanurazvan committed Jun 7, 2023
1 parent 1ae88c7 commit ba7945c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 83 deletions.
37 changes: 24 additions & 13 deletions src/components/organisms/NewResourceWizard/NewResourceWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import {first} from 'lodash';
import path from 'path';

import {useAppDispatch, useAppSelector} from '@redux/hooks';
import {setAlert} from '@redux/reducers/alert';
import {closeNewResourceWizard} from '@redux/reducers/ui';
import {registeredKindHandlersSelector} from '@redux/selectors/resourceKindSelectors';
import {useResourceContentMapRef, useResourceMetaMap} from '@redux/selectors/resourceMapSelectors';
import {joinK8sResource} from '@redux/services/resource';
import {getResourceKindSchema} from '@redux/services/schema';
import {createTransientResource} from '@redux/services/transientResource';
import {saveTransientResources} from '@redux/thunks/saveTransientResources';
import {saveResourceToFileFolder} from '@redux/thunks/saveResourceToFileFolder';

import {useFileSelectOptions} from '@hooks/useFileSelectOptions';
import {useFileFolderTreeSelectData} from '@hooks/useFolderTreeSelectData';
Expand All @@ -30,12 +31,15 @@ import {getResourceKindHandler} from '@src/kindhandlers';

import {ROOT_FILE_ENTRY} from '@shared/constants/fileEntry';
import {hotkeys} from '@shared/constants/hotkeys';
import {AlertEnum} from '@shared/models/alert';
import {FileMapType} from '@shared/models/appState';
import {FileEntry} from '@shared/models/fileEntry';
import {K8sResource, ResourceMeta} from '@shared/models/k8sResource';
import {ResourceSavingDestination} from '@shared/models/resourceCreate';
import {ResourceKindHandler} from '@shared/models/resourceKindHandler';
import {NewResourceWizardInput} from '@shared/models/ui';
import {openNamespaceTopic, openUniqueObjectNameTopic} from '@shared/utils/shell';
import {trackEvent} from '@shared/utils/telemetry';

import {FileCategoryLabel, FileNameLabel, SaveDestinationWrapper, StyledSelect} from './NewResourceWizard.styled';

Expand Down Expand Up @@ -93,7 +97,8 @@ const NewResourceWizard = () => {
const [isResourceKindNamespaced, setIsResourceKindNamespaced] = useState<boolean>(true);
const [isSubmitDisabled, setSubmitDisabled] = useState(true);
const [exportFileName, setExportFileName] = useState<string | undefined>('');
const [savingDestination, setSavingDestination, savingDestinationRef] = useStateWithRef<string>('doNotSave');
const [savingDestination, setSavingDestination, savingDestinationRef] =
useStateWithRef<ResourceSavingDestination>('doNotSave');
const [selectedFile, setSelectedFile, selectedFileRef] = useStateWithRef<string | undefined>(undefined);
const [selectedFolder, setSelectedFolder, selectedFolderRef] = useStateWithRef(ROOT_FILE_ENTRY);
const [generateRandom, setGenerateRandom, generateRandomRef] = useStateWithRef<boolean>(false);
Expand Down Expand Up @@ -227,7 +232,7 @@ const NewResourceWizard = () => {
setSavingDestination('saveToFolder');
setSelectedFolder(defaultInput.targetFolder);
} else if (defaultInput?.targetFile && isFolderOpen) {
setSavingDestination('saveToFile');
setSavingDestination('appendToFile');
setSelectedFile(defaultInput.targetFile);
} else {
setSavingDestination('doNotSave');
Expand Down Expand Up @@ -420,9 +425,12 @@ const NewResourceWizard = () => {
},
dispatch,
'local',
jsonTemplate
jsonTemplate,
savingDestinationRef.current !== 'doNotSave' ? true : undefined
);

trackEvent('create/resource', {resourceKind: newResource.kind});

if (savingDestinationRef.current !== 'doNotSave') {
let absolutePath;

Expand All @@ -432,20 +440,23 @@ const NewResourceWizard = () => {
selectedFolderRef.current === ROOT_FILE_ENTRY
? path.join(rootFolderEntryRef.current.filePath, path.sep, fullFileName)
: path.join(rootFolderEntryRef.current.filePath, selectedFolderRef.current, path.sep, fullFileName);
} else if (savingDestinationRef.current === 'saveToFile' && selectedFileRef.current) {
} else if (savingDestinationRef.current === 'appendToFile' && selectedFileRef.current) {
absolutePath = path.join(rootFolderEntryRef.current.filePath, selectedFileRef.current);
} else {
absolutePath = path.join(rootFolderEntryRef.current.filePath, path.sep, fullFileName);
}

dispatch(
saveTransientResources({
resourcePayloads: [{resource: newResource, absolutePath}],
saveMode: savingDestinationRef.current === 'saveToFolder' ? savingDestinationRef.current : 'appendToFile',
})
);
dispatch(saveResourceToFileFolder({resource: newResource, absolutePath, saveMode: savingDestinationRef.current}));
}

dispatch(
setAlert({
title: 'Resource created',
message: `Successfully created ${newResource.name}`,
type: AlertEnum.Success,
})
);

setSavingDestination('doNotSave');
closeWizard();
}, [
Expand Down Expand Up @@ -649,7 +660,7 @@ const NewResourceWizard = () => {
<SaveDestinationWrapper compact>
<StyledSelect value={savingDestination} onChange={handleSavingDestinationChange}>
<Option value="saveToFolder">Save to folder</Option>
<Option value="saveToFile">Add to file</Option>
<Option value="appendToFile">Add to file</Option>
<Option value="doNotSave">Don't save</Option>
</StyledSelect>
{savingDestination === 'saveToFolder' && (
Expand All @@ -665,7 +676,7 @@ const NewResourceWizard = () => {
treeNodeLabelProp="label"
/>
)}
{savingDestination === 'saveToFile' && (
{savingDestination === 'appendToFile' && (
<StyledSelect
showSearch
onChange={(value: any) => setSelectedFile(value)}
Expand Down
8 changes: 6 additions & 2 deletions src/redux/services/transientResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export function createTransientResource(
input: {name: string; kind: string; apiVersion: string; namespace?: string},
dispatch: AppDispatch,
createdIn: 'local' | 'cluster',
jsonTemplate?: Partial<K8sObject>
jsonTemplate?: Partial<K8sObject>,
saveToFileOrFolder?: boolean
) {
const newResourceId = uuidv4();
let newResourceText: string;
Expand Down Expand Up @@ -76,7 +77,10 @@ export function createTransientResource(
object: newResourceObject,
isClusterScoped: getResourceKindHandler(input.kind)?.isNamespaced || false,
};
dispatch(addResource(newResource));

if (!saveToFileOrFolder) {
dispatch(addResource(newResource));
}

return newResource;
}
Expand Down
75 changes: 75 additions & 0 deletions src/redux/thunks/saveResourceToFileFolder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {createAsyncThunk} from '@reduxjs/toolkit';

import fs from 'fs';
import util from 'util';

import {YAML_DOCUMENT_DELIMITER} from '@constants/constants';

import {getFileTimestamp, hasValidExtension} from '@utils/files';

import {ROOT_FILE_ENTRY} from '@shared/constants/fileEntry';
import {AppDispatch} from '@shared/models/appDispatch';
import {K8sResource} from '@shared/models/k8sResource';
import {ResourceSavingDestination} from '@shared/models/resourceCreate';
import {RootState} from '@shared/models/rootState';

const readFilePromise = util.promisify(fs.readFile);
const appendFilePromise = util.promisify(fs.appendFile);
const writeFilePromise = util.promisify(fs.writeFile);

export type SaveResourceToFileFolderPayload = {
resourceRange: {start: number; length: number} | undefined;
fileTimestamp: number | undefined;
};
type SaveResourceToFileFolderArgs = {
resource: K8sResource;
absolutePath: string;
saveMode: ResourceSavingDestination;
};

export const saveResourceToFileFolder = createAsyncThunk<
SaveResourceToFileFolderPayload,
SaveResourceToFileFolderArgs,
{dispatch: AppDispatch; state: RootState}
>('main/saveResourceToFileFolder', async (payload, thunkAPI) => {
const mainState = thunkAPI.getState().main;
const rootFolder = mainState.fileMap[ROOT_FILE_ENTRY];

const {absolutePath, resource, saveMode} = payload;

let resourceRange: {start: number; length: number} | undefined;

if (!rootFolder) {
throw new Error('Could not find the root folder.');
}

if (saveMode === 'saveToFolder') {
await writeFilePromise(absolutePath, resource.text);
} else if (saveMode === 'appendToFile') {
const fileName = absolutePath.split('\\').pop();

if (!hasValidExtension(fileName, ['.yaml', '.yml'])) {
throw new Error('The selected file does not have .yaml extension.');
}

const fileContent = await readFilePromise(absolutePath, 'utf-8');
let contentToAppend = resource.text;
if (fileContent.trim().length > 0) {
if (fileContent.trim().endsWith(YAML_DOCUMENT_DELIMITER)) {
contentToAppend = `\n${resource.text}`;
} else {
contentToAppend = `\n${YAML_DOCUMENT_DELIMITER}\n${resource.text}`;
}
}

resourceRange = {
start: fileContent.length,
length: contentToAppend.length,
};

await appendFilePromise(absolutePath, contentToAppend);
}
const fileTimestamp = getFileTimestamp(absolutePath);

return {resourceRange, fileTimestamp};
});
72 changes: 4 additions & 68 deletions src/redux/thunks/saveTransientResources.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import {createAsyncThunk} from '@reduxjs/toolkit';

import fs from 'fs';
import util from 'util';

import {YAML_DOCUMENT_DELIMITER} from '@constants/constants';

import {fileIsExcluded} from '@redux/services/fileEntry';

import {getFileTimestamp, hasValidExtension} from '@utils/files';

import {ROOT_FILE_ENTRY} from '@shared/constants/fileEntry';
import {AppDispatch} from '@shared/models/appDispatch';
import {FileEntry} from '@shared/models/fileEntry';
import {K8sResource} from '@shared/models/k8sResource';
import {RootState} from '@shared/models/rootState';
import {trackEvent} from '@shared/utils/telemetry';

import {SaveResourceToFileFolderPayload, saveResourceToFileFolder} from './saveResourceToFileFolder';
import {createRejectionWithAlert} from './utils';

const ERROR_TITLE = 'Resource Save Failed';
Expand All @@ -34,54 +25,6 @@ type SaveMultipleTransientResourcesArgs = {
saveMode: 'saveToFolder' | 'appendToFile';
};

const readFilePromise = util.promisify(fs.readFile);
const appendFilePromise = util.promisify(fs.appendFile);
const writeFilePromise = util.promisify(fs.writeFile);

const performSaveTransientResource = async (
resource: K8sResource,
rootFolder: FileEntry | undefined,
absolutePath: string,
saveMode: 'saveToFolder' | 'appendToFile'
) => {
let resourceRange: {start: number; length: number} | undefined;
if (!rootFolder) {
throw new Error('Could not find the root folder.');
}

trackEvent('create/resource', {resourceKind: resource.kind});

if (saveMode === 'saveToFolder') {
await writeFilePromise(absolutePath, resource.text);
} else {
const fileName = absolutePath.split('\\').pop();

if (!hasValidExtension(fileName, ['.yaml', '.yml'])) {
throw new Error('The selected file does not have .yaml extension.');
}

const fileContent = await readFilePromise(absolutePath, 'utf-8');
let contentToAppend = resource.text;
if (fileContent.trim().length > 0) {
if (fileContent.trim().endsWith(YAML_DOCUMENT_DELIMITER)) {
contentToAppend = `\n${resource.text}`;
} else {
contentToAppend = `\n${YAML_DOCUMENT_DELIMITER}\n${resource.text}`;
}
}

resourceRange = {
start: fileContent.length,
length: contentToAppend.length,
};

await appendFilePromise(absolutePath, contentToAppend);
}
const fileTimestamp = getFileTimestamp(absolutePath);

return {resourceRange, fileTimestamp};
};

export const saveTransientResources = createAsyncThunk<
SaveMultipleTransientResourcesPayload,
SaveMultipleTransientResourcesArgs,
Expand All @@ -90,22 +33,15 @@ export const saveTransientResources = createAsyncThunk<
state: RootState;
}
>('main/saveTransientResources', async (args, thunkAPI) => {
const mainState = thunkAPI.getState().main;
const rootFolder = mainState.fileMap[ROOT_FILE_ENTRY];

let resourcePayloads: ResourcePayload[] = [];

for (let i = 0; i < args.resourcePayloads.length; i += 1) {
const {resource, absolutePath} = args.resourcePayloads[i];

try {
// eslint-disable-next-line no-await-in-loop
const {resourceRange, fileTimestamp} = await performSaveTransientResource(
resource,
rootFolder,
absolutePath,
args.saveMode
);
const {resourceRange, fileTimestamp} = (
await thunkAPI.dispatch(saveResourceToFileFolder({resource, absolutePath, saveMode: args.saveMode}))
).payload as SaveResourceToFileFolderPayload;

resourcePayloads.push({
resourceId: resource.id,
Expand Down
2 changes: 2 additions & 0 deletions src/shared/models/resourceCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export type NewResourceAction = {
fromTypeLabel: 'AI' | 'advanced template' | 'model';
onClick: () => void;
};

export type ResourceSavingDestination = 'doNotSave' | 'saveToFolder' | 'appendToFile';

0 comments on commit ba7945c

Please sign in to comment.