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)}
/>
+