fix(ui): issue form\

This commit is contained in:
Shekar Siri 2025-02-04 11:32:53 +01:00
parent 1b3a3dfc21
commit 82621012de
7 changed files with 328 additions and 311 deletions

View file

@ -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);

View 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;

View file

@ -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;

View file

@ -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>
); );
} }

View file

@ -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',

View file

@ -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);
}
} }
};
} }

View file

@ -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,
};
} }
} }