fix(ui): issue form\
This commit is contained in:
parent
1b3a3dfc21
commit
82621012de
7 changed files with 328 additions and 311 deletions
|
|
@ -1,178 +0,0 @@
|
||||||
import { observer } from 'mobx-react-lite';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { useStore } from 'App/mstore';
|
|
||||||
import { Button, CircularLoader, Form, Input, Loader } from 'UI';
|
|
||||||
|
|
||||||
import Select from 'Shared/Select';
|
|
||||||
|
|
||||||
const SelectedValue = ({ icon, text }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center">
|
|
||||||
{icon}
|
|
||||||
<span>{text}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function IssueForm(props) {
|
|
||||||
const { closeHandler } = props;
|
|
||||||
const { issueReportingStore } = useStore();
|
|
||||||
const creating = issueReportingStore.createLoading;
|
|
||||||
const projects = issueReportingStore.projects;
|
|
||||||
const projectsLoading = issueReportingStore.projectsLoading;
|
|
||||||
const users = issueReportingStore.users;
|
|
||||||
const instance = issueReportingStore.instance;
|
|
||||||
const metaLoading = issueReportingStore.metaLoading;
|
|
||||||
const issueTypes = issueReportingStore.issueTypes;
|
|
||||||
const addActivity = issueReportingStore.saveIssue;
|
|
||||||
const init = issueReportingStore.init;
|
|
||||||
const edit = issueReportingStore.editInstance;
|
|
||||||
const fetchMeta = issueReportingStore.fetchMeta;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
init({
|
|
||||||
projectId: projects[0] ? projects[0].id : '',
|
|
||||||
issueType: issueTypes[0] ? issueTypes[0].id : '',
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (instance?.projectId) {
|
|
||||||
fetchMeta(instance?.projectId).then(() => {
|
|
||||||
edit({
|
|
||||||
issueType: '',
|
|
||||||
assignee: '',
|
|
||||||
projectId: instance?.projectId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [instance?.projectId]);
|
|
||||||
|
|
||||||
const onSubmit = () => {
|
|
||||||
const { sessionId } = props;
|
|
||||||
addActivity(sessionId, instance).then(() => {
|
|
||||||
const { errors } = props;
|
|
||||||
if (!errors || errors.length === 0) {
|
|
||||||
init({ projectId: instance?.projectId });
|
|
||||||
void issueReportingStore.fetchList(sessionId);
|
|
||||||
closeHandler();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const write = (e) => {
|
|
||||||
const {
|
|
||||||
target: { name, value },
|
|
||||||
} = e;
|
|
||||||
edit({ [name]: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeOption = ({ name, value }) => edit({ [name]: value.value });
|
|
||||||
const projectOptions = projects.map(({ name, id }) => ({
|
|
||||||
label: name,
|
|
||||||
value: id,
|
|
||||||
}));
|
|
||||||
const userOptions = users.map(({ name, id }) => ({ label: name, value: id }));
|
|
||||||
|
|
||||||
const issueTypeOptions = issueTypes.map(({ name, id, iconUrl, color }) => {
|
|
||||||
return { label: name, value: id, iconUrl, color };
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedIssueType = issueTypes.filter(
|
|
||||||
(issue) => issue.id == instance?.issueType
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Loader loading={projectsLoading} size={40}>
|
|
||||||
<Form onSubmit={onSubmit} className="text-left">
|
|
||||||
<Form.Field className="mb-15-imp">
|
|
||||||
<label htmlFor="issueType" className="flex items-center">
|
|
||||||
<span className="mr-2">Project</span>
|
|
||||||
<CircularLoader loading={metaLoading} />
|
|
||||||
</label>
|
|
||||||
<Select
|
|
||||||
name="projectId"
|
|
||||||
options={projectOptions}
|
|
||||||
defaultValue={instance?.projectId}
|
|
||||||
fluid
|
|
||||||
onChange={writeOption}
|
|
||||||
placeholder="Project"
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
<Form.Field className="mb-15-imp">
|
|
||||||
<label htmlFor="issueType">Issue Type</label>
|
|
||||||
<Select
|
|
||||||
selection
|
|
||||||
name="issueType"
|
|
||||||
labeled
|
|
||||||
options={issueTypeOptions}
|
|
||||||
defaultValue={instance?.issueType}
|
|
||||||
fluid
|
|
||||||
onChange={writeOption}
|
|
||||||
placeholder="Select issue type"
|
|
||||||
text={
|
|
||||||
selectedIssueType ? (
|
|
||||||
<SelectedValue
|
|
||||||
icon={selectedIssueType.iconUrl}
|
|
||||||
text={selectedIssueType.name}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
|
|
||||||
<Form.Field className="mb-15-imp">
|
|
||||||
<label htmlFor="assignee">Assignee</label>
|
|
||||||
<Select
|
|
||||||
selection
|
|
||||||
name="assignee"
|
|
||||||
options={userOptions}
|
|
||||||
fluid
|
|
||||||
onChange={writeOption}
|
|
||||||
placeholder="Select a user"
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
|
|
||||||
<Form.Field className="mb-15-imp">
|
|
||||||
<label htmlFor="title">Summary</label>
|
|
||||||
<Input
|
|
||||||
name="title"
|
|
||||||
value={instance?.title}
|
|
||||||
placeholder="Issue Title / Summary"
|
|
||||||
onChange={write}
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
|
|
||||||
<Form.Field className="mb-15-imp">
|
|
||||||
<label htmlFor="description">Description</label>
|
|
||||||
<textarea
|
|
||||||
name="description"
|
|
||||||
rows="2"
|
|
||||||
value={instance?.description}
|
|
||||||
placeholder="E.g. Found this issue at 3:29secs"
|
|
||||||
onChange={write}
|
|
||||||
className="text-area"
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
loading={creating}
|
|
||||||
variant="primary"
|
|
||||||
disabled={!instance?.isValid}
|
|
||||||
className="float-left mr-2"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{'Create'}
|
|
||||||
</Button>
|
|
||||||
<Button type="button" onClick={closeHandler}>
|
|
||||||
{'Cancel'}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</Loader>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default observer(IssueForm);
|
|
||||||
177
frontend/app/components/Session_/Issues/IssueForm.tsx
Normal file
177
frontend/app/components/Session_/Issues/IssueForm.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useStore } from 'App/mstore';
|
||||||
|
import { CircularLoader, Loader } from 'UI';
|
||||||
|
import { Form, Input, Button, Select } from 'antd';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
closeHandler: () => void;
|
||||||
|
sessionId: string;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const IssueForm: React.FC<Props> = observer(({ closeHandler, sessionId, errors }) => {
|
||||||
|
const {
|
||||||
|
issueReportingStore: {
|
||||||
|
createLoading: creating,
|
||||||
|
projects,
|
||||||
|
projectsLoading,
|
||||||
|
users,
|
||||||
|
instance,
|
||||||
|
metaLoading,
|
||||||
|
issueTypes,
|
||||||
|
saveIssue,
|
||||||
|
init,
|
||||||
|
editInstance: edit,
|
||||||
|
fetchMeta,
|
||||||
|
fetchList
|
||||||
|
}
|
||||||
|
} = useStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init({
|
||||||
|
projectId: projects[0]?.id || '',
|
||||||
|
issueType: issueTypes[0]?.id || ''
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (instance?.projectId) {
|
||||||
|
fetchMeta(instance.projectId).then(() => {
|
||||||
|
edit({ issueType: '', assignee: '', projectId: instance.projectId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [instance?.projectId]);
|
||||||
|
|
||||||
|
const onFinish = async () => {
|
||||||
|
await saveIssue(sessionId, instance).then(() => {
|
||||||
|
closeHandler();
|
||||||
|
}).catch(() => {
|
||||||
|
toast('Failed to create issue', { type: 'error' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (!errors || errors.length === 0) {
|
||||||
|
// init({ projectId: instance?.projectId });
|
||||||
|
// fetchList(sessionId);
|
||||||
|
// closeHandler();
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||||
|
) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
edit({ [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (field: string) => (value: string) => {
|
||||||
|
edit({ [field]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectOptions = projects.map(
|
||||||
|
({ name, id }: { name: string; id: string }) => ({ label: name, value: id })
|
||||||
|
);
|
||||||
|
const userOptions = users.map(
|
||||||
|
({ name, id }: { name: string; id: string }) => ({ label: name, value: id })
|
||||||
|
);
|
||||||
|
const issueTypeOptions = issueTypes.map((opt: any) => ({
|
||||||
|
label: opt.name,
|
||||||
|
value: opt.id,
|
||||||
|
iconUrl: opt.iconUrl
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Loader loading={projectsLoading} size={40}>
|
||||||
|
<Form onFinish={onFinish} layout="vertical" className="text-left">
|
||||||
|
<Form.Item
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<span className="mr-2">Project</span>
|
||||||
|
<CircularLoader loading={metaLoading} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
className="mb-15-imp"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={instance?.projectId}
|
||||||
|
onChange={handleSelect('projectId')}
|
||||||
|
placeholder="Project"
|
||||||
|
>
|
||||||
|
{projectOptions.map((option) => (
|
||||||
|
<Select.Option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Issue Type" className="mb-15-imp">
|
||||||
|
<Select
|
||||||
|
value={instance?.issueType}
|
||||||
|
onChange={handleSelect('issueType')}
|
||||||
|
placeholder="Select issue type"
|
||||||
|
optionLabelProp="label"
|
||||||
|
>
|
||||||
|
{issueTypeOptions.map((option) => (
|
||||||
|
<Select.Option
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
label={
|
||||||
|
<div className="flex items-center">
|
||||||
|
{option.iconUrl}
|
||||||
|
<span>{option.label}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Assignee" className="mb-15-imp">
|
||||||
|
<Select
|
||||||
|
value={instance?.assignee}
|
||||||
|
onChange={handleSelect('assignee')}
|
||||||
|
placeholder="Select a user"
|
||||||
|
>
|
||||||
|
{userOptions.map((option) => (
|
||||||
|
<Select.Option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Summary" className="mb-15-imp">
|
||||||
|
<Input
|
||||||
|
name="title"
|
||||||
|
value={instance?.title}
|
||||||
|
placeholder="Issue Title / Summary"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Description" className="mb-15-imp">
|
||||||
|
<Input.TextArea
|
||||||
|
name="description"
|
||||||
|
rows={2}
|
||||||
|
value={instance?.description}
|
||||||
|
placeholder="E.g. Found this issue at 3:29secs"
|
||||||
|
onChange={handleChange}
|
||||||
|
className="text-area"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
loading={creating}
|
||||||
|
disabled={!instance?.isValid}
|
||||||
|
className="float-left mr-2"
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Button onClick={closeHandler}>Cancel</Button>
|
||||||
|
</Form>
|
||||||
|
</Loader>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IssueForm;
|
||||||
|
|
@ -1,20 +1,25 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import stl from './issuesModal.module.css';
|
import stl from './issuesModal.module.css';
|
||||||
import IssueForm from './IssueForm';
|
import IssueForm from './IssueForm';
|
||||||
import cn from 'classnames'
|
import cn from 'classnames';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sessionId: string;
|
||||||
|
closeHandler: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const IssuesModal = ({
|
const IssuesModal = ({
|
||||||
sessionId,
|
sessionId,
|
||||||
closeHandler,
|
closeHandler
|
||||||
}) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn(stl.wrapper, 'h-screen')}>
|
<div className={cn(stl.wrapper, 'h-screen')}>
|
||||||
<h3 className="text-xl font-semibold">
|
<h3 className="text-xl font-semibold">
|
||||||
<span>Create Issue</span>
|
<span>Create Issue</span>
|
||||||
</h3>
|
</h3>
|
||||||
<IssueForm sessionId={ sessionId } closeHandler={ closeHandler } />
|
<IssueForm sessionId={sessionId} closeHandler={closeHandler} errors={[]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default IssuesModal;
|
export default IssuesModal;
|
||||||
|
|
@ -11,6 +11,7 @@ const Key = ({ label }: { label: string }) => (
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function Cell({ shortcut, text }: any) {
|
function Cell({ shortcut, text }: any) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 justify-start rounded">
|
<div className="flex items-center gap-2 justify-start rounded">
|
||||||
|
|
@ -39,8 +40,7 @@ export const PlaybackSpeedShortcut = () => <Key label={'↑ / ↓'} />;
|
||||||
|
|
||||||
export function ShortcutGrid() {
|
export function ShortcutGrid() {
|
||||||
return (
|
return (
|
||||||
<div className={'p-4 overflow-y-auto h-screen'}>
|
|
||||||
<div className={'mb-4 font-semibold text-xl'}>Keyboard Shortcuts</div>
|
|
||||||
<div className=" grid grid-cols-1 grid-flow-row-dense auto-cols-max gap-4 justify-items-start">
|
<div className=" grid grid-cols-1 grid-flow-row-dense auto-cols-max gap-4 justify-items-start">
|
||||||
<Cell shortcut="⇧ + U" text="Copy Session URL with time" />
|
<Cell shortcut="⇧ + U" text="Copy Session URL with time" />
|
||||||
<Cell shortcut="⇧ + C" text="Launch Console" />
|
<Cell shortcut="⇧ + C" text="Launch Console" />
|
||||||
|
|
@ -61,7 +61,6 @@ export function ShortcutGrid() {
|
||||||
<Cell shortcut="↑" text="Playback Speed Up" />
|
<Cell shortcut="↑" text="Playback Speed Up" />
|
||||||
<Cell shortcut="↓" text="Playback Speed Down" />
|
<Cell shortcut="↓" text="Playback Speed Down" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { ShareAltOutlined } from '@ant-design/icons';
|
import { ShareAltOutlined } from '@ant-design/icons';
|
||||||
import { Button as AntButton, Switch, Tooltip, Dropdown } from 'antd';
|
import { Button as AntButton, Switch, Tooltip, Dropdown } from 'antd';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { useModal } from "Components/Modal";
|
import IssuesModal from 'Components/Session_/Issues/IssuesModal';
|
||||||
import IssuesModal from "Components/Session_/Issues/IssuesModal";
|
|
||||||
import { Link2, Keyboard } from 'lucide-react';
|
import { Link2, Keyboard } from 'lucide-react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { MoreOutlined } from '@ant-design/icons'
|
import { MoreOutlined } from '@ant-design/icons';
|
||||||
import { Icon } from 'UI';
|
import { Icon } from 'UI';
|
||||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||||
import { IFRAME } from 'App/constants/storageKeys';
|
import { IFRAME } from 'App/constants/storageKeys';
|
||||||
|
|
@ -15,12 +14,14 @@ import { checkParam, truncateStringToFit } from 'App/utils';
|
||||||
import SessionTabs from 'Components/Session/Player/SharedComponents/SessionTabs';
|
import SessionTabs from 'Components/Session/Player/SharedComponents/SessionTabs';
|
||||||
import { ShortcutGrid } from 'Components/Session_/Player/Controls/components/KeyboardHelp';
|
import { ShortcutGrid } from 'Components/Session_/Player/Controls/components/KeyboardHelp';
|
||||||
import WarnBadge from 'Components/Session_/WarnBadge';
|
import WarnBadge from 'Components/Session_/WarnBadge';
|
||||||
import { toast } from "react-toastify";
|
import { toast } from 'react-toastify';
|
||||||
import HighlightButton from './Highlight/HighlightButton';
|
import HighlightButton from './Highlight/HighlightButton';
|
||||||
|
|
||||||
import SharePopup from '../shared/SharePopup/SharePopup';
|
import SharePopup from '../shared/SharePopup/SharePopup';
|
||||||
import QueueControls from './QueueControls';
|
import QueueControls from './QueueControls';
|
||||||
import { Bookmark as BookmarkIcn, BookmarkCheck, Vault } from "lucide-react";
|
import { Bookmark as BookmarkIcn, BookmarkCheck, Vault } from 'lucide-react';
|
||||||
|
import { useModal } from 'Components/ModalContext';
|
||||||
|
import IssueForm from 'Components/Session_/Issues/IssueForm';
|
||||||
|
|
||||||
const disableDevtools = 'or_devtools_uxt_toggle';
|
const disableDevtools = 'or_devtools_uxt_toggle';
|
||||||
|
|
||||||
|
|
@ -31,11 +32,11 @@ function SubHeader(props) {
|
||||||
sessionStore,
|
sessionStore,
|
||||||
projectsStore,
|
projectsStore,
|
||||||
userStore,
|
userStore,
|
||||||
issueReportingStore,
|
issueReportingStore
|
||||||
} = useStore();
|
} = useStore();
|
||||||
const favorite = sessionStore.current.favorite;
|
const favorite = sessionStore.current.favorite;
|
||||||
const isEnterprise = userStore.isEnterprise;
|
const isEnterprise = userStore.isEnterprise;
|
||||||
const currentSession = sessionStore.current
|
const currentSession = sessionStore.current;
|
||||||
const projectId = projectsStore.siteId;
|
const projectId = projectsStore.siteId;
|
||||||
const integrations = integrationsStore.issues.list;
|
const integrations = integrationsStore.issues.list;
|
||||||
const { store } = React.useContext(PlayerContext);
|
const { store } = React.useContext(PlayerContext);
|
||||||
|
|
@ -43,7 +44,7 @@ function SubHeader(props) {
|
||||||
const hasIframe = localStorage.getItem(IFRAME) === 'true';
|
const hasIframe = localStorage.getItem(IFRAME) === 'true';
|
||||||
const [hideTools, setHideTools] = React.useState(false);
|
const [hideTools, setHideTools] = React.useState(false);
|
||||||
const [isFavorite, setIsFavorite] = React.useState(favorite);
|
const [isFavorite, setIsFavorite] = React.useState(favorite);
|
||||||
const { showModal, hideModal } = useModal();
|
const { openModal, closeModal } = useModal();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const hideDevtools = checkParam('hideTools');
|
const hideDevtools = checkParam('hideTools');
|
||||||
|
|
@ -62,7 +63,7 @@ function SubHeader(props) {
|
||||||
|
|
||||||
const issuesIntegrationList = integrationsStore.issues.list;
|
const issuesIntegrationList = integrationsStore.issues.list;
|
||||||
const handleOpenIssueModal = () => {
|
const handleOpenIssueModal = () => {
|
||||||
issueReportingStore.init();
|
issueReportingStore.init({});
|
||||||
if (!issueReportingStore.projectsFetched) {
|
if (!issueReportingStore.projectsFetched) {
|
||||||
issueReportingStore.fetchProjects().then((projects) => {
|
issueReportingStore.fetchProjects().then((projects) => {
|
||||||
if (projects && projects[0]) {
|
if (projects && projects[0]) {
|
||||||
|
|
@ -70,13 +71,12 @@ function SubHeader(props) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
showModal(
|
openModal(
|
||||||
<IssuesModal
|
<IssueForm sessionId={currentSession.sessionId} closeHandler={closeModal} errors={[]} />,
|
||||||
provider={reportingProvider}
|
{
|
||||||
sessionId={currentSession.sessionId}
|
title: 'Create Issue'
|
||||||
closeHandler={hideModal}
|
}
|
||||||
/>
|
);
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reportingProvider = issuesIntegrationList[0]?.provider || '';
|
const reportingProvider = issuesIntegrationList[0]?.provider || '';
|
||||||
|
|
@ -92,8 +92,8 @@ function SubHeader(props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const showKbHelp = () => {
|
const showKbHelp = () => {
|
||||||
showModal(<ShortcutGrid />, { right: true, width: 320 });
|
openModal(<ShortcutGrid />, { width: 320, title: 'Keyboard Shortcuts' });
|
||||||
}
|
};
|
||||||
|
|
||||||
const vaultIcon = isEnterprise ? (
|
const vaultIcon = isEnterprise ? (
|
||||||
<Vault size={16} strokeWidth={1} />
|
<Vault size={16} strokeWidth={1} />
|
||||||
|
|
@ -115,7 +115,7 @@ function SubHeader(props) {
|
||||||
toast.success(isFavorite ? REMOVED_MESSAGE : ADDED_MESSAGE);
|
toast.success(isFavorite ? REMOVED_MESSAGE : ADDED_MESSAGE);
|
||||||
setIsFavorite(!isFavorite);
|
setIsFavorite(!isFavorite);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -126,13 +126,13 @@ function SubHeader(props) {
|
||||||
? props.live
|
? props.live
|
||||||
? '#F6FFED'
|
? '#F6FFED'
|
||||||
: '#EBF4F5'
|
: '#EBF4F5'
|
||||||
: undefined,
|
: undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WarnBadge
|
<WarnBadge
|
||||||
siteId={projectId}
|
siteId={projectId!}
|
||||||
currentLocation={currentLocation}
|
currentLocation={currentLocation}
|
||||||
version={currentSession?.trackerVersion ?? undefined}
|
version={currentSession?.trackerVersion ?? ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SessionTabs />
|
<SessionTabs />
|
||||||
|
|
@ -180,7 +180,7 @@ function SubHeader(props) {
|
||||||
<span>Issues</span>
|
<span>Issues</span>
|
||||||
</div>,
|
</div>,
|
||||||
disabled: !enabledIntegration,
|
disabled: !enabledIntegration,
|
||||||
onClick: handleOpenIssueModal,
|
onClick: handleOpenIssueModal
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
|
|
@ -1,60 +1,66 @@
|
||||||
import { makeAutoObservable } from 'mobx';
|
import { makeAutoObservable } from 'mobx';
|
||||||
import ReportedIssue from "../types/session/assignment";
|
import ReportedIssue from '../types/session/assignment';
|
||||||
import { issueReportsService } from "App/services";
|
import { issueReportsService } from 'App/services';
|
||||||
|
import { makePersistable } from '.store/mobx-persist-store-virtual-858ce4d906/package';
|
||||||
|
|
||||||
export default class issueReportingStore {
|
export default class IssueReportingStore {
|
||||||
instance: ReportedIssue
|
instance!: ReportedIssue;
|
||||||
issueTypes: any[] = []
|
issueTypes: any[] = [];
|
||||||
list: any[] = []
|
list: any[] = [];
|
||||||
issueTypeIcons: {}
|
issueTypeIcons: Record<string, string> = {};
|
||||||
users: any[] = []
|
users: any[] = [];
|
||||||
projects: any[] = []
|
projects: any[] = [];
|
||||||
issuesFetched = false
|
issuesFetched = false;
|
||||||
projectsFetched = false
|
projectsFetched = false;
|
||||||
projectsLoading = false
|
projectsLoading = false;
|
||||||
metaLoading = false
|
metaLoading = false;
|
||||||
createLoading = false
|
createLoading = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
|
// void makePersistable(this, {
|
||||||
|
// name: 'IssueReportingStore',
|
||||||
|
// properties: ['issueTypes', 'list', 'projects', 'users', 'projectsFetched', 'issuesFetched', 'issueTypeIcons'],
|
||||||
|
// expireIn: 60000 * 10,
|
||||||
|
// removeOnExpiration: true,
|
||||||
|
// storage: window.localStorage
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
init = (instance: any) => {
|
init = (instanceData: any) => {
|
||||||
this.instance = new ReportedIssue(instance);
|
this.instance = new ReportedIssue(instanceData);
|
||||||
if (this.issueTypes.length > 0) {
|
if (this.issueTypes.length > 0) {
|
||||||
this.instance.issueType = this.issueTypes[0].id;
|
this.instance.issueType = this.issueTypes[0].id;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
editInstance = (data: any) => {
|
editInstance = (data: Partial<ReportedIssue>) => {
|
||||||
const inst = this.instance
|
Object.assign(this.instance, data);
|
||||||
this.instance = new ReportedIssue({ ...inst, ...data })
|
};
|
||||||
}
|
|
||||||
|
|
||||||
setList = (list: any[]) => {
|
setList = (list: any[]) => {
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.issuesFetched = true
|
this.issuesFetched = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
setProjects = (projects: any[]) => {
|
setProjects = (projects: any[]) => {
|
||||||
this.projectsFetched = true;
|
this.projectsFetched = true;
|
||||||
this.projects = projects;
|
this.projects = projects;
|
||||||
}
|
};
|
||||||
|
|
||||||
setMeta = (data: any) => {
|
setMeta = (data: any) => {
|
||||||
const issueTypes = data.issueTypes || [];
|
const issueTypes = data.issueTypes || [];
|
||||||
const itIcons = {}
|
const itIcons: Record<string, string> = {};
|
||||||
issueTypes.forEach((it: any) => {
|
issueTypes.forEach((it: any) => {
|
||||||
itIcons[it.id] = it.iconUrl
|
itIcons[it.id] = it.iconUrl;
|
||||||
})
|
});
|
||||||
|
|
||||||
this.issueTypes = issueTypes;
|
this.issueTypes = issueTypes;
|
||||||
this.issueTypeIcons = itIcons;
|
this.issueTypeIcons = itIcons;
|
||||||
this.users = data.users || [];
|
this.users = data.users || [];
|
||||||
}
|
};
|
||||||
|
|
||||||
fetchProjects = async () => {
|
fetchProjects = async () => {
|
||||||
if (this.projectsLoading) return;
|
if (this.projectsLoading || this.projects.length > 0) return;
|
||||||
this.projectsLoading = true;
|
this.projectsLoading = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await issueReportsService.fetchProjects();
|
const { data } = await issueReportsService.fetchProjects();
|
||||||
|
|
@ -62,11 +68,11 @@ export default class issueReportingStore {
|
||||||
this.projectsFetched = true;
|
this.projectsFetched = true;
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.projectsLoading = false;
|
this.projectsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
fetchMeta = async (projectId: number) => {
|
fetchMeta = async (projectId: number) => {
|
||||||
if (this.metaLoading) return;
|
if (this.metaLoading) return;
|
||||||
|
|
@ -75,32 +81,36 @@ export default class issueReportingStore {
|
||||||
const { data } = await issueReportsService.fetchMeta(projectId);
|
const { data } = await issueReportsService.fetchMeta(projectId);
|
||||||
this.setMeta(data);
|
this.setMeta(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.metaLoading = false;
|
this.metaLoading = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
saveIssue = async (sessionId: string, params: any) => {
|
saveIssue = async (sessionId: string, instance: ReportedIssue) => {
|
||||||
if (this.createLoading) return;
|
if (this.createLoading) return;
|
||||||
this.createLoading = true;
|
this.createLoading = true;
|
||||||
try {
|
try {
|
||||||
const data = { ...params, assignee: params.assignee, issueType: params.issueType }
|
const payload = { ...instance, assignee: instance.assignee + '', issueType: instance.issueType + '' };
|
||||||
const { data: issue } = await issueReportsService.saveIssue(sessionId, data);
|
const resp = await issueReportsService.saveIssue(sessionId, payload);
|
||||||
this.init(issue)
|
// const resp = await issueReportsService.saveIssue(sessionId, instance.toCreate());
|
||||||
|
|
||||||
|
// const { data: issue } = await issueReportsService.saveIssue(sessionId, payload);
|
||||||
|
this.init(resp.data.issue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
throw e;
|
||||||
|
// console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.createLoading = false;
|
this.createLoading = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
fetchList = async (sessionId: string) => {
|
fetchList = async (sessionId: string) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await issueReportsService.fetchIssueIntegrations(sessionId);
|
const { data } = await issueReportsService.fetchIssueIntegrations(sessionId);
|
||||||
this.setList(data);
|
this.setList(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
|
import { makeAutoObservable } from 'mobx';
|
||||||
import Activity, { IActivity } from './activity';
|
import Activity, { IActivity } from './activity';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { notEmptyString } from 'App/validate';
|
import { notEmptyString } from 'App/validate';
|
||||||
|
|
||||||
interface IReportedIssue {
|
export interface IReportedIssue {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
timestamp: number;
|
timestamp: number | null;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
siteId: string;
|
siteId: string;
|
||||||
activities: [];
|
activities: any[];
|
||||||
closed: boolean;
|
closed: boolean;
|
||||||
assignee: string;
|
assignee: string;
|
||||||
commentsCount: number;
|
commentsCount: number;
|
||||||
|
|
@ -17,58 +18,61 @@ interface IReportedIssue {
|
||||||
description: string;
|
description: string;
|
||||||
iconUrl: string;
|
iconUrl: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
comments: IActivity[]
|
comments: IActivity[];
|
||||||
users: { id: string }[]
|
users: { id: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ReportedIssue {
|
export default class ReportedIssue {
|
||||||
id: IReportedIssue["id"];
|
id: IReportedIssue['id'] = '';
|
||||||
title: IReportedIssue["title"] = '';
|
title: IReportedIssue['title'] = '';
|
||||||
timestamp: IReportedIssue["timestamp"];
|
timestamp: DateTime | null = null;
|
||||||
sessionId: IReportedIssue["sessionId"];
|
sessionId: IReportedIssue['sessionId'] = '';
|
||||||
projectId: IReportedIssue["projectId"] = '';
|
projectId: IReportedIssue['projectId'] = '';
|
||||||
siteId: IReportedIssue["siteId"];
|
siteId: IReportedIssue['siteId'] = '';
|
||||||
activities: IReportedIssue["activities"];
|
activities: any[] = [];
|
||||||
closed: IReportedIssue["closed"];
|
closed: IReportedIssue['closed'] = false;
|
||||||
assignee: IReportedIssue["assignee"] = '';
|
assignee: IReportedIssue['assignee'] = '';
|
||||||
issueType: IReportedIssue["issueType"] = '';
|
issueType: IReportedIssue['issueType'] = '';
|
||||||
description: IReportedIssue["description"] = '';
|
description: IReportedIssue['description'] = '';
|
||||||
iconUrl: IReportedIssue["iconUrl"] = '';
|
iconUrl: IReportedIssue['iconUrl'] = '';
|
||||||
|
|
||||||
constructor(assignment?: IReportedIssue) {
|
constructor(assignment?: IReportedIssue) {
|
||||||
|
makeAutoObservable(this);
|
||||||
if (assignment) {
|
if (assignment) {
|
||||||
Object.assign(this, {
|
Object.assign(this, assignment);
|
||||||
...assignment,
|
this.timestamp = assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : null;
|
||||||
timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined,
|
this.activities = assignment.comments
|
||||||
activities: assignment.comments ? assignment.comments.map(activity => {
|
? assignment.comments.map((activity) => {
|
||||||
if (assignment.users) {
|
if (assignment.users) {
|
||||||
// @ts-ignore ???
|
// @ts-ignore
|
||||||
activity.user = assignment.users.filter(user => user.id === activity.author)[0];
|
activity.user = assignment.users.find(user => user.id === activity.author);
|
||||||
}
|
}
|
||||||
return new Activity(activity)
|
return new Activity(activity);
|
||||||
}) : []
|
|
||||||
})
|
})
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS() {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
validate() {
|
||||||
return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description)
|
return (
|
||||||
|
!!this.projectId &&
|
||||||
|
!!this.issueType &&
|
||||||
|
notEmptyString(this.title) &&
|
||||||
|
notEmptyString(this.description)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isValid() {
|
get isValid() {
|
||||||
return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description)
|
return this.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
toCreate() {
|
toCreate() {
|
||||||
return {
|
return {
|
||||||
title: this.title,
|
title: this.title,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
assignee: this.assignee,
|
assignee: this.assignee + '',
|
||||||
issueType: this.issueType
|
issueType: this.issueType + '',
|
||||||
}
|
projectId: this.projectId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue