From 3803576aa416e6e72219ccb62e52b03a055d3619 Mon Sep 17 00:00:00 2001 From: Antti Sonkeri Date: Sat, 20 Jul 2024 11:41:56 +0300 Subject: [PATCH] feat: Tagging requests and filtering collection runs using tags --- .../RequestPane/GraphQLRequestPane/index.js | 7 ++ .../RequestPane/HttpRequestPane/index.js | 7 ++ .../RequestPane/Tags/TagList/StyledWrapper.js | 25 +++++++ .../RequestPane/Tags/TagList/TagList.js | 69 +++++++++++++++++++ .../src/components/RequestPane/Tags/index.js | 43 ++++++++++++ .../src/components/RunnerResults/index.jsx | 46 ++++++++++++- .../CollectionItem/RunCollectionItem/index.js | 40 ++++++++++- .../ReduxStore/slices/collections/actions.js | 5 +- .../ReduxStore/slices/collections/index.js | 40 ++++++++++- .../bruno-app/src/utils/collections/index.js | 6 +- packages/bruno-cli/src/commands/run.js | 28 +++++++- packages/bruno-cli/src/utils/bru.js | 1 + packages/bruno-common/src/index.ts | 3 +- packages/bruno-common/src/tags/index.spec.ts | 43 ++++++++++++ packages/bruno-common/src/tags/index.ts | 13 ++++ packages/bruno-electron/src/bru/index.js | 9 ++- .../bruno-electron/src/ipc/network/index.js | 12 +++- packages/bruno-lang/v2/src/bruToJson.js | 28 +++++++- packages/bruno-lang/v2/src/jsonToBru.js | 8 +++ .../bruno-lang/v2/tests/fixtures/request.bru | 5 ++ .../bruno-lang/v2/tests/fixtures/request.json | 1 + .../bruno-schema/src/collections/index.js | 3 +- .../src/collections/requestSchema.spec.js | 1 + packages/bruno-toml/src/jsonToToml.js | 4 ++ packages/bruno-toml/src/tomlToJson.js | 4 ++ .../bruno-toml/tests/methods/get/request.json | 1 + .../bruno-toml/tests/methods/get/request.toml | 2 + 27 files changed, 434 insertions(+), 20 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestPane/Tags/TagList/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Tags/TagList/TagList.js create mode 100644 packages/bruno-app/src/components/RequestPane/Tags/index.js create mode 100644 packages/bruno-common/src/tags/index.spec.ts create mode 100644 packages/bruno-common/src/tags/index.ts diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js index 34558d928..dc42c883f 100644 --- a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js @@ -19,6 +19,7 @@ import StyledWrapper from './StyledWrapper'; import Documentation from 'components/Documentation/index'; import GraphQLSchemaActions from '../GraphQLSchemaActions/index'; import HeightBoundContainer from 'ui/HeightBoundContainer'; +import Tags from 'components/RequestPane/Tags/index'; const GraphQLRequestPane = ({ item, collection, onSchemaLoad, toggleDocs, handleGqlClickReference }) => { const dispatch = useDispatch(); @@ -101,6 +102,9 @@ const GraphQLRequestPane = ({ item, collection, onSchemaLoad, toggleDocs, handle case 'docs': { return ; } + case 'tags': { + return ; + } default: { return
404 | Not found
; } @@ -152,6 +156,9 @@ const GraphQLRequestPane = ({ item, collection, onSchemaLoad, toggleDocs, handle
selectTab('docs')}> Docs
+
selectTab('tags')}> + Tags +
diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index 2a2acbb21..72c222f57 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -18,6 +18,7 @@ import HeightBoundContainer from 'ui/HeightBoundContainer'; import { useEffect } from 'react'; import StatusDot from 'components/StatusDot'; import Settings from 'components/RequestPane/Settings'; +import Tags from 'components/RequestPane/Tags/index'; const HttpRequestPane = ({ item, collection }) => { const dispatch = useDispatch(); @@ -65,6 +66,9 @@ const HttpRequestPane = ({ item, collection }) => { case 'settings': { return ; } + case 'tags': { + return ; + } default: { return
404 | Not found
; } @@ -165,6 +169,9 @@ const HttpRequestPane = ({ item, collection }) => {
selectTab('settings')}> Settings
+
selectTab('tags')}> + Tags +
{focusedTab.requestPaneTab === 'body' ? (
diff --git a/packages/bruno-app/src/components/RequestPane/Tags/TagList/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Tags/TagList/StyledWrapper.js new file mode 100644 index 000000000..651fc761b --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Tags/TagList/StyledWrapper.js @@ -0,0 +1,25 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + input[type='text'] { + border: solid 1px transparent; + outline: none !important; + background-color: inherit; + + &:focus { + outline: none !important; + border: solid 1px transparent; + } + } + + li { + display: flex; + align-items: center; + border: 1px solid ${(props) => props.theme.text}; + border-radius: 5px; + padding-inline: 5px; + background: ${(props) => props.theme.sidebar.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Tags/TagList/TagList.js b/packages/bruno-app/src/components/RequestPane/Tags/TagList/TagList.js new file mode 100644 index 000000000..7248d54da --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Tags/TagList/TagList.js @@ -0,0 +1,69 @@ +import { IconX } from '@tabler/icons'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import StyledWrapper from './StyledWrapper'; + +const TagList = ({ tags, onTagRemove, onTagAdd }) => { + const tagNameRegex = /^[\w-]+$/; + const [isEditing, setIsEditing] = useState(false); + const [text, setText] = useState(''); + + const handleChange = (e) => { + setText(e.target.value); + }; + + const handleKeyDown = (e) => { + if (e.code == 'Escape') { + setText(''); + setIsEditing(false); + return; + } + if (e.code !== 'Enter' && e.code !== 'Space') { + return; + } + if (!tagNameRegex.test(text)) { + toast.error('Tags must only contain alpha-numeric characters, "-", "_"'); + return; + } + if (tags.includes(text)) { + toast.error(`Tag "${text}" already exists`); + return; + } + onTagAdd(text); + setText(''); + setIsEditing(false); + }; + + return ( + +
    + {tags && tags.length + ? tags.map((_tag) => ( +
  • + {_tag} + +
  • + )) + : null} +
+ {isEditing ? ( + + ) : ( + + )} +
+ ); +}; + +export default TagList; diff --git a/packages/bruno-app/src/components/RequestPane/Tags/index.js b/packages/bruno-app/src/components/RequestPane/Tags/index.js new file mode 100644 index 000000000..5f035fa88 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Tags/index.js @@ -0,0 +1,43 @@ +import 'github-markdown-css/github-markdown.css'; +import get from 'lodash/get'; +import { addRequestTag, deleteRequestTag } from 'providers/ReduxStore/slices/collections'; +import { useDispatch } from 'react-redux'; +import TagList from './TagList/TagList'; + +const Tags = ({ item, collection }) => { + const tags = item.draft ? get(item, 'draft.request.tags') : get(item, 'request.tags'); + + const dispatch = useDispatch(); + + const handleAdd = (_tag) => { + dispatch( + addRequestTag({ + tag: _tag, + itemUid: item.uid, + collectionUid: collection.uid + }) + ); + }; + + const handleRemove = (_tag) => { + dispatch( + deleteRequestTag({ + tag: _tag, + itemUid: item.uid, + collectionUid: collection.uid + }) + ); + }; + + if (!item) { + return null; + } + + return ( +
+ +
+ ); +}; + +export default Tags; diff --git a/packages/bruno-app/src/components/RunnerResults/index.jsx b/packages/bruno-app/src/components/RunnerResults/index.jsx index c4945c7aa..f09e305d3 100644 --- a/packages/bruno-app/src/components/RunnerResults/index.jsx +++ b/packages/bruno-app/src/components/RunnerResults/index.jsx @@ -9,6 +9,7 @@ import { IconRefresh, IconCircleCheck, IconCircleX, IconCircleOff, IconCheck, Ic import ResponsePane from './ResponsePane'; import StyledWrapper from './StyledWrapper'; import { areItemsLoading } from 'utils/collections'; +import TagList from 'components/RequestPane/Tags/TagList/TagList'; const getDisplayName = (fullPath, pathname, name = '') => { let relativePath = path.relative(fullPath, pathname); @@ -42,6 +43,8 @@ export default function RunnerResults({ collection }) { const dispatch = useDispatch(); const [selectedItem, setSelectedItem] = useState(null); const [delay, setDelay] = useState(null); + const [tags, setTags] = useState({ include: [], exclude: [] }); + const [tagsEnabled, setTagsEnabled] = useState(false); // ref for the runner output body const runnerBodyRef = useRef(); @@ -88,11 +91,19 @@ export default function RunnerResults({ collection }) { .filter(Boolean); const runCollection = () => { - dispatch(runCollectionFolder(collection.uid, null, true, Number(delay))); + dispatch(runCollectionFolder(collection.uid, null, true, Number(delay), tagsEnabled && tags)); }; const runAgain = () => { - dispatch(runCollectionFolder(collection.uid, runnerInfo.folderUid, runnerInfo.isRecursive, Number(delay))); + dispatch( + runCollectionFolder( + collection.uid, + runnerInfo.folderUid, + runnerInfo.isRecursive, + Number(delay), + tagsEnabled && tags + ) + ); }; const resetRunner = () => { @@ -140,6 +151,37 @@ export default function RunnerResults({ collection }) { onChange={(e) => setDelay(e.target.value)} />
+
+
+ + setTagsEnabled(!tagsEnabled)} + /> +
+ {tagsEnabled && ( +
+
+ Included tags: + setTags({ ...tags, include: [...tags.include, tag] })} + onTagRemove={(tag) => setTags({ ...tags, include: tags.include.filter((t) => t !== tag) })} + /> +
+
+ Excluded tags: + setTags({ ...tags, exclude: [...tags.exclude, tag] })} + onTagRemove={(tag) => setTags({ ...tags, exclude: tags.exclude.filter((t) => t !== tag) })} + /> +
+
+ )} +