mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-22 07:53:34 +01:00
Feat/UI feedback : Visual + console feedback for failed postman translated imports (#2316)
* feat(translation-feedback): console log incomplete postman import translations with stats and details * feat(translation-feedback): warn instead of log, reformat layout * feat(translation-feedback): optional callback function, update index.spec.js * feat(ui-feedback): display translation errors in the UI before choosing import location * feat(ui-feedback): syntax fix --------- Co-authored-by: bpoulaindev <bpoulainpro@gmail.com>
This commit is contained in:
parent
ff3ea33979
commit
91b5d0123e
@ -1,11 +1,110 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import Modal from 'components/Modal';
|
||||
import { IconAlertTriangle, IconArrowRight, IconCaretDown, IconCaretRight, IconCopy } from '@tabler/icons';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) => {
|
||||
const TranslationLog = ({ translationLog }) => {
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const preventSetShowDetails = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowDetails(!showDetails);
|
||||
};
|
||||
const copyClipboard = (e, value) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(value);
|
||||
toast.success('Copied to clipboard');
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col mt-2">
|
||||
<div className="border-l-2 border-amber-500 dark:border-amber-300 bg-amber-50 dark:bg-amber-50/10 p-1.5 rounded-r">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<IconAlertTriangle className="h-4 w-4 text-amber-500 dark:text-amber-300" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-2">
|
||||
<p className="text-xs text-amber-700 dark:text-amber-300">
|
||||
<span className="font-semibold">Warning:</span> Some commands were not translated.{' '}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => preventSetShowDetails(e)}
|
||||
className="flex w-fit items-center rounded px-2.5 py-1 mt-2 text-xs font-semibold ring-1 ring-inset bg-slate-50 dark:bg-slate-400/10 text-slate-700 dark:text-slate-300 ring-slate-600/10 dark:ring-slate-400/20"
|
||||
>
|
||||
See details
|
||||
{showDetails ? <IconCaretDown size={16} className="ml-1" /> : <IconCaretRight size={16} className="ml-1" />}
|
||||
</button>
|
||||
{showDetails && (
|
||||
<div className="flex relative flex-col text-xs max-w-[364px] max-h-[300px] overflow-scroll mt-2 p-2 bg-slate-50 dark:bg-slate-400/10 ring-1 ring-inset rounded text-slate-700 dark:text-slate-300 ring-slate-600/20 dark:ring-slate-400/20">
|
||||
<span className="font-semibold flex items-center">
|
||||
Impacted Collections: {Object.keys(translationLog || {}).length}
|
||||
</span>
|
||||
<span className="font-semibold flex items-center">
|
||||
Impacted Lines:{' '}
|
||||
{Object.values(translationLog || {}).reduce(
|
||||
(acc, curr) => acc + (curr.script?.length || 0) + (curr.test?.length || 0),
|
||||
0
|
||||
)}
|
||||
</span>
|
||||
<span className="my-1">
|
||||
The numbers after 'script' and 'test' indicate the line numbers of incomplete translations.
|
||||
</span>
|
||||
<ul>
|
||||
{Object.entries(translationLog || {}).map(([name, value]) => (
|
||||
<li key={name} className="list-none text-xs font-semibold">
|
||||
<div className="font-semibold flex items-center text-xs whitespace-nowrap">
|
||||
<IconCaretRight className="min-w-4 max-w-4 -ml-1" />
|
||||
{name}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{value.script && (
|
||||
<div className="flex items-center text-xs font-light mb-1">
|
||||
<span className="mr-2">
|
||||
test :
|
||||
{value.script.map((scriptValue, index) => (
|
||||
<span className="flex items-center" key={`script_${name}_${index}`}>
|
||||
<span className="text-xs font-light">{scriptValue}</span>
|
||||
{index < value.script.length - 1 && <> - </>}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{value.test && (
|
||||
<div className="flex items-center text-xs font-light mb-1">
|
||||
<span className="mr-2">test :</span>
|
||||
{value.test.map((testValue, index) => (
|
||||
<div className="flex items-center" key={`test_${name}_${index}`}>
|
||||
<span className="text-xs font-light">{testValue}</span>
|
||||
{index < value.test.length - 1 && <> - </>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<button
|
||||
className="absolute top-1 right-1 flex w-fit items-center rounded p-2 text-xs font-semibold ring-1 ring-inset bg-slate-50 dark:bg-slate-400/10 text-slate-700 dark:text-slate-300 ring-slate-600/10 dark:ring-slate-400/20"
|
||||
onClick={(e) => copyClipboard(e, JSON.stringify(translationLog))}
|
||||
>
|
||||
<IconCopy size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName, translationLog }) => {
|
||||
const inputRef = useRef();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@ -24,7 +123,6 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
|
||||
handleSubmit(values.collectionLocation);
|
||||
}
|
||||
});
|
||||
|
||||
const browse = () => {
|
||||
dispatch(browseDirectory())
|
||||
.then((dirPath) => {
|
||||
@ -52,7 +150,9 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
|
||||
Name
|
||||
</label>
|
||||
<div className="mt-2">{collectionName}</div>
|
||||
|
||||
{translationLog && Object.keys(translationLog).length > 0 && (
|
||||
<TranslationLog translationLog={translationLog} />
|
||||
)}
|
||||
<>
|
||||
<label htmlFor="collectionLocation" className="block font-semibold mt-3">
|
||||
Location
|
||||
|
@ -14,14 +14,16 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const TitleBar = () => {
|
||||
const [importedCollection, setImportedCollection] = useState(null);
|
||||
const [importedTranslationLog, setImportedTranslationLog] = useState({});
|
||||
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
|
||||
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
const handleImportCollection = (collection) => {
|
||||
const handleImportCollection = ({ collection, translationLog }) => {
|
||||
setImportedCollection(collection);
|
||||
setImportedTranslationLog(translationLog);
|
||||
setImportCollectionModalOpen(false);
|
||||
setImportCollectionLocationModalOpen(true);
|
||||
};
|
||||
@ -64,6 +66,7 @@ const TitleBar = () => {
|
||||
{importCollectionLocationModalOpen ? (
|
||||
<ImportCollectionLocation
|
||||
collectionName={importedCollection.name}
|
||||
translationLog={importedTranslationLog}
|
||||
onClose={() => setImportCollectionLocationModalOpen(false)}
|
||||
handleSubmit={handleImportCollectionLocation}
|
||||
/>
|
||||
|
@ -13,6 +13,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
const Welcome = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [importedCollection, setImportedCollection] = useState(null);
|
||||
const [importedTranslationLog, setImportedTranslationLog] = useState({});
|
||||
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
|
||||
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
|
||||
@ -23,8 +24,9 @@ const Welcome = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleImportCollection = (collection) => {
|
||||
const handleImportCollection = (collection, translationLog) => {
|
||||
setImportedCollection(collection);
|
||||
setImportedTranslationLog(translationLog);
|
||||
setImportCollectionModalOpen(false);
|
||||
setImportCollectionLocationModalOpen(true);
|
||||
};
|
||||
@ -44,6 +46,7 @@ const Welcome = () => {
|
||||
) : null}
|
||||
{importCollectionLocationModalOpen ? (
|
||||
<ImportCollectionLocation
|
||||
translationLog={importedTranslationLog}
|
||||
collectionName={importedCollection.name}
|
||||
onClose={() => setImportCollectionLocationModalOpen(false)}
|
||||
handleSubmit={handleImportCollectionLocation}
|
||||
|
@ -54,6 +54,8 @@ const convertV21Auth = (array) => {
|
||||
}, {});
|
||||
};
|
||||
|
||||
const translationLog = {};
|
||||
|
||||
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
|
||||
brunoParent.items = brunoParent.items || [];
|
||||
const folderMap = {};
|
||||
@ -114,7 +116,25 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
||||
}
|
||||
}
|
||||
};
|
||||
/* struct of translation log
|
||||
{
|
||||
[collectionName]: {
|
||||
script: [index1, index2],
|
||||
test: [index1, index2]
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// type could be script or test
|
||||
const pushTranslationLog = (type, index) => {
|
||||
if (!translationLog[i.name]) {
|
||||
translationLog[i.name] = {};
|
||||
}
|
||||
if (!translationLog[i.name][type]) {
|
||||
translationLog[i.name][type] = [];
|
||||
}
|
||||
translationLog[i.name][type].push(index + 1);
|
||||
};
|
||||
if (i.event) {
|
||||
i.event.forEach((event) => {
|
||||
if (event.listen === 'prerequest' && event.script && event.script.exec) {
|
||||
@ -123,11 +143,15 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
||||
}
|
||||
if (Array.isArray(event.script.exec)) {
|
||||
brunoRequestItem.request.script.req = event.script.exec
|
||||
.map((line) => (options.enablePostmanTranslations.enabled ? postmanTranslation(line) : `// ${line}`))
|
||||
.map((line, index) =>
|
||||
options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(line, () => pushTranslationLog('script', index))
|
||||
: `// ${line}`
|
||||
)
|
||||
.join('\n');
|
||||
} else {
|
||||
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(event.script.exec[0])
|
||||
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('script', 0))
|
||||
: `// ${event.script.exec[0]} `;
|
||||
}
|
||||
}
|
||||
@ -137,11 +161,15 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
||||
}
|
||||
if (Array.isArray(event.script.exec)) {
|
||||
brunoRequestItem.request.tests = event.script.exec
|
||||
.map((line) => (options.enablePostmanTranslations.enabled ? postmanTranslation(line) : `// ${line}`))
|
||||
.map((line, index) =>
|
||||
options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(line, () => pushTranslationLog('test', index))
|
||||
: `// ${line}`
|
||||
)
|
||||
.join('\n');
|
||||
} else {
|
||||
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(event.script.exec[0])
|
||||
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('test', 0))
|
||||
: `// ${event.script.exec[0]} `;
|
||||
}
|
||||
}
|
||||
@ -313,6 +341,21 @@ const parsePostmanCollection = (str, options) => {
|
||||
});
|
||||
};
|
||||
|
||||
const logTranslationDetails = (translationLog) => {
|
||||
if (Object.keys(translationLog || {}).length > 0) {
|
||||
console.warn(
|
||||
`[Postman Translation Logs]
|
||||
Collections incomplete : ${Object.keys(translationLog || {}).length}` +
|
||||
`\nTotal lines incomplete : ${Object.values(translationLog || {}).reduce(
|
||||
(acc, curr) => acc + (curr.script?.length || 0) + (curr.test?.length || 0),
|
||||
0
|
||||
)}` +
|
||||
`\nSee details below :`,
|
||||
translationLog
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const importCollection = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fileDialog({ accept: 'application/json' })
|
||||
@ -321,11 +364,12 @@ const importCollection = (options) => {
|
||||
.then(transformItemsInCollection)
|
||||
.then(hydrateSeqInCollection)
|
||||
.then(validateSchema)
|
||||
.then((collection) => resolve(collection))
|
||||
.then((collection) => resolve({ collection, translationLog }))
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
reject(new BrunoError('Import collection failed'));
|
||||
});
|
||||
})
|
||||
.then(() => logTranslationDetails(translationLog));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,9 @@ const replacements = {
|
||||
'pm\\.response\\.to\\.have\\.status\\(': 'expect(res.getStatus()).to.equal(',
|
||||
'pm\\.response\\.json\\(': 'res.getBody(',
|
||||
'pm\\.expect\\(': 'expect(',
|
||||
'pm\\.environment\\.has\\(([^)]+)\\)': 'bru.getEnvVar($1) !== undefined && bru.getEnvVar($1) !== null'
|
||||
'pm\\.environment\\.has\\(([^)]+)\\)': 'bru.getEnvVar($1) !== undefined && bru.getEnvVar($1) !== null',
|
||||
'pm\\.response\\.code': 'res.getStatus()',
|
||||
'pm\\.response\\.text\\(': 'res.getBody()?.toString('
|
||||
};
|
||||
|
||||
const compiledReplacements = Object.entries(replacements).map(([pattern, replacement]) => ({
|
||||
@ -19,7 +21,7 @@ const compiledReplacements = Object.entries(replacements).map(([pattern, replace
|
||||
replacement
|
||||
}));
|
||||
|
||||
export const postmanTranslation = (script) => {
|
||||
export const postmanTranslation = (script, logCallback) => {
|
||||
try {
|
||||
let modifiedScript = script;
|
||||
let modified = false;
|
||||
@ -29,8 +31,9 @@ export const postmanTranslation = (script) => {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
if (modified && modifiedScript.includes('pm.')) {
|
||||
if (modifiedScript.includes('pm.')) {
|
||||
modifiedScript = modifiedScript.replace(/^(.*pm\..*)$/gm, '// $1');
|
||||
logCallback?.();
|
||||
}
|
||||
return modifiedScript;
|
||||
} catch (e) {
|
||||
|
Loading…
Reference in New Issue
Block a user