ui: move assignments to issueReportingStore.ts
This commit is contained in:
parent
1a2e143888
commit
5d3872c371
27 changed files with 324 additions and 1074 deletions
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button } from 'UI';
|
||||
import { resetActiveIsue } from 'Duck/issues';
|
||||
|
||||
const ActiveIssueClose = ({ resetActiveIsue }) => {
|
||||
return (
|
||||
<div className="absolute right-0 top-0 mr-4 mt-4">
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={ resetActiveIsue }
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, {
|
||||
resetActiveIsue
|
||||
})(ActiveIssueClose);
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
class ActivityList extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
Hello
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ActivityList;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
const AuthorAvatar = ({ className, imgUrl, width = 32, height = 32 }) => {
|
||||
return (
|
||||
<div className={ cn(className, "img-crcle")}>
|
||||
<img src={ imgUrl } alt="" width={ width } height={ height } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthorAvatar;
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { CodeBlock } from 'UI';
|
||||
|
||||
import stl from './contentRender.module.css';
|
||||
|
||||
const elType = {
|
||||
PARAGRAPH: 'paragraph',
|
||||
TEXT: 'text',
|
||||
QUOTE: 'blockquote',
|
||||
CODE_BLOCK: 'codeBlock',
|
||||
MENTION: 'mention',
|
||||
RULE: 'rule',
|
||||
HARD_BREAK: 'hardBreak',
|
||||
};
|
||||
|
||||
const renderElement = (el, provider) => {
|
||||
if (provider === 'github') return el;
|
||||
|
||||
switch (el.type) {
|
||||
case elType.PARAGRAPH:
|
||||
return (
|
||||
<p className={stl.para}>
|
||||
<ContentRender message={el} />
|
||||
</p>
|
||||
);
|
||||
case elType.QUOTE:
|
||||
return (
|
||||
<blockquote className={stl.quote}>
|
||||
<ContentRender message={el} />
|
||||
</blockquote>
|
||||
);
|
||||
case elType.CODE_BLOCK:
|
||||
return (
|
||||
<CodeBlock
|
||||
code={codeRender(el.content)[0]}
|
||||
language={el.attrs.language || ''}
|
||||
/>
|
||||
);
|
||||
// return <CodeMirror
|
||||
// className={ stl.codeMirror }
|
||||
// value={ codeRender(el.content)[0] }
|
||||
// options={{
|
||||
// mode: el.attrs.language || '',
|
||||
// theme: 'material',
|
||||
// lineNumbers: true,
|
||||
// readOnly: true,
|
||||
// showCursorWhenSelecting: false,
|
||||
// scroll: true
|
||||
// }}
|
||||
// />
|
||||
case elType.MENTION:
|
||||
return <span className={stl.mention}>{`@${el.attrs.text}`}</span>;
|
||||
case elType.RULE:
|
||||
return <hr className={stl.rule} />;
|
||||
case elType.HARD_BREAK:
|
||||
return <br />;
|
||||
case elType.RULE:
|
||||
return <hr className={stl.rule} />;
|
||||
case elType.TEXT:
|
||||
return el.text;
|
||||
}
|
||||
return <ContentRender key={el.text} message={el} />;
|
||||
};
|
||||
|
||||
const codeRender = (content) => content.map((el) => el.text);
|
||||
|
||||
const ContentRender = (props) => {
|
||||
const { message, provider } = props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{provider === 'github'
|
||||
? message
|
||||
: message &&
|
||||
message.content &&
|
||||
message.content.map((el) => (
|
||||
<React.Fragment>{renderElement(el, provider)}</React.Fragment>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentRender;
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import { checkRecentTime } from 'App/date';
|
||||
import AuthorAvatar from './AuthoAvatar';
|
||||
import ContentRender from './ContentRender';
|
||||
|
||||
const IssueComment = ({ activity, provider }) => {
|
||||
return (
|
||||
<div className="mb-4 flex">
|
||||
<AuthorAvatar
|
||||
className="flex-shrink-0 mr-4"
|
||||
imgUrl={ activity.user && activity.user.avatarUrls['24x24'] }
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<div className="flex flex-col flex-1 mb-2">
|
||||
<div className="flex">
|
||||
<span className="font-medium mr-3">{ activity.user && activity.user.name }</span>
|
||||
<span className="text-sm ">{ activity.createdAt && checkRecentTime(activity.createdAt) }</span>
|
||||
</div>
|
||||
<div>{ <ContentRender message={ activity.message } provider={provider} /> }</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IssueComment;
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Input, Button, Form } from 'UI';
|
||||
import { addMessage } from 'Duck/assignments';
|
||||
|
||||
class IssueCommentForm extends React.PureComponent {
|
||||
state = { comment: '' }
|
||||
|
||||
write = (e) => {
|
||||
e.stopPropagation();
|
||||
const { target: { name, value } } = e
|
||||
this.setState({ comment: value });
|
||||
}
|
||||
|
||||
addComment = () => {
|
||||
const { comment } = this.state;
|
||||
const { sessionId, issueId, addMessage, loading } = this.props;
|
||||
if (loading) return;
|
||||
|
||||
addMessage(sessionId, issueId, { message: comment, type: 'message' }).then(() => {
|
||||
this.setState({comment: ''});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { comment } = this.state;
|
||||
const { loading } = this.props;
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-white">
|
||||
<h5 className="mb-1">Add Comment</h5>
|
||||
<Form onSubmit={ this.addComment }>
|
||||
<div className="flex mt-2 items-center">
|
||||
<Input
|
||||
onChange={ this.write }
|
||||
value={ comment }
|
||||
fluid
|
||||
placeholder="Type here"
|
||||
className="flex-1 mr-3"
|
||||
/>
|
||||
<Button
|
||||
disabled={ comment.length === 0 }
|
||||
variant="primary"
|
||||
loading={ loading }>{'Comment'}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(state => ({
|
||||
loading: state.getIn(['assignments', 'addMessage', 'loading'])
|
||||
}), { addMessage })(IssueCommentForm)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import React from 'react';
|
||||
import ContentRender from './ContentRender';
|
||||
|
||||
const IssueDescription = ({ className, description, provider }) => {
|
||||
return (
|
||||
<div className={ className }>
|
||||
<h5 className="mb-4">Description</h5>
|
||||
<ContentRender message={ description } provider={provider} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IssueDescription;
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Loader } from 'UI';
|
||||
import IssueHeader from './IssueHeader';
|
||||
import IssueCommentForm from './IssueCommentForm';
|
||||
import IssueComment from './IssueComment';
|
||||
import stl from './issueDetails.module.css';
|
||||
import IssueDescription from './IssueDescription';
|
||||
|
||||
class IssueDetails extends React.PureComponent {
|
||||
state = { searchQuery: ''}
|
||||
|
||||
write = (e, { name, value }) => this.setState({ [ name ]: value });
|
||||
|
||||
render() {
|
||||
const { sessionId, issue, loading, users, issueTypeIcons, issuesIntegration } = this.props;
|
||||
const activities = issue.activities;
|
||||
const provider = issuesIntegration.provider;
|
||||
const assignee = users.filter(({id}) => issue.assignee === id).first();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Loader loading={ loading }>
|
||||
<div>
|
||||
<IssueHeader
|
||||
issue={ issue }
|
||||
typeIcon={ issueTypeIcons[issue.issueType] }
|
||||
assignee={ assignee }
|
||||
onSearchComment={ this.write }
|
||||
/>
|
||||
<div className={ cn( stl.activitiesList, 'p-6') }>
|
||||
<IssueDescription
|
||||
className="mb-10"
|
||||
description={ issue.description }
|
||||
provider={provider}
|
||||
/>
|
||||
|
||||
{ activities.size > 0 && <h5 className="mb-4">Comments</h5>}
|
||||
{ activities.map(activity => (
|
||||
<IssueComment activity={ activity } key={activity.key} provider={provider} />
|
||||
))}
|
||||
</div>
|
||||
<IssueCommentForm sessionId={ sessionId } issueId={ issue.id } />
|
||||
</div>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
users: state.getIn(['assignments', 'users']),
|
||||
loading: state.getIn(['assignments', 'fetchAssignment', 'loading']),
|
||||
issueTypeIcons: state.getIn(['assignments', 'issueTypeIcons']),
|
||||
issuesIntegration: state.getIn([ 'issues', 'list'])[0] || {},
|
||||
}))(IssueDetails);
|
||||
|
|
@ -1,182 +1,178 @@
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Form, Input, Button, CircularLoader, Loader } from 'UI';
|
||||
import { addActivity, init, edit, fetchAssignments, fetchMeta } from 'Duck/assignments';
|
||||
|
||||
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">
|
||||
{/* <img className="mr-2" src={ icon } width="13" height="13" /> */}
|
||||
{icon}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class IssueForm extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
const { projects, issueTypes } = this.props;
|
||||
this.props.init({
|
||||
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;
|
||||
|
||||
console.log(users, instance, projects, issueTypes)
|
||||
React.useEffect(() => {
|
||||
init({
|
||||
projectId: projects[0] ? projects[0].id : '',
|
||||
issueType: issueTypes[0] ? issueTypes[0].id : '',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { instance } = this.props;
|
||||
if (newProps.instance.projectId && newProps.instance.projectId != instance.projectId) {
|
||||
this.props.fetchMeta(newProps.instance.projectId).then(() => {
|
||||
this.props.edit({ issueType: '', assignee: '', projectId: newProps.instance.projectId });
|
||||
React.useEffect(() => {
|
||||
if (instance?.projectId) {
|
||||
fetchMeta(instance?.projectId).then(() => {
|
||||
edit({
|
||||
issueType: '',
|
||||
assignee: '',
|
||||
projectId: instance?.projectId,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [instance?.projectId]);
|
||||
|
||||
onSubmit = () => {
|
||||
const { sessionId, addActivity } = this.props;
|
||||
const { instance } = this.props;
|
||||
|
||||
addActivity(sessionId, instance.toJS()).then(() => {
|
||||
const { errors } = this.props;
|
||||
const onSubmit = () => {
|
||||
const { sessionId } = props;
|
||||
addActivity(sessionId, instance).then(() => {
|
||||
const { errors } = props;
|
||||
if (!errors || errors.length === 0) {
|
||||
this.props.init({ projectId: instance.projectId });
|
||||
this.props.fetchAssignments(sessionId);
|
||||
this.props.closeHandler();
|
||||
init({ projectId: instance?.projectId });
|
||||
closeHandler();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
write = (e) => {
|
||||
const write = (e) => {
|
||||
const {
|
||||
target: { name, value },
|
||||
} = e;
|
||||
this.props.edit({ [name]: value });
|
||||
edit({ [name]: value });
|
||||
};
|
||||
writeOption = ({ name, value }) => this.props.edit({ [name]: value.value });
|
||||
|
||||
render() {
|
||||
const {
|
||||
creating,
|
||||
projects,
|
||||
users,
|
||||
issueTypes,
|
||||
instance,
|
||||
closeHandler,
|
||||
metaLoading,
|
||||
projectsLoading,
|
||||
} = this.props;
|
||||
const projectOptions = projects.map(({ name, id }) => ({ label: name, value: id })).toArray();
|
||||
const userOptions = users.map(({ name, id }) => ({ label: name, value: id })).toArray();
|
||||
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 issueTypeOptions = issueTypes.map(({ name, id, iconUrl, color }) => {
|
||||
return { label: name, value: id, iconUrl, color };
|
||||
});
|
||||
|
||||
const selectedIssueType = issueTypes.filter((issue) => issue.id == instance.issueType)[0];
|
||||
const selectedIssueType = issueTypes.filter(
|
||||
(issue) => issue.id == instance?.issueType
|
||||
)[0];
|
||||
|
||||
return (
|
||||
<Loader loading={projectsLoading} size={40}>
|
||||
<Form onSubmit={this.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={this.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={this.writeOption}
|
||||
placeholder="Select issue type"
|
||||
text={
|
||||
selectedIssueType ? (
|
||||
<SelectedValue icon={selectedIssueType.iconUrl} text={selectedIssueType.name} />
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Form.Field>
|
||||
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={this.writeOption}
|
||||
placeholder="Select a user"
|
||||
/>
|
||||
</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={this.write}
|
||||
/>
|
||||
</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
|
||||
{/* <span className="text-sm text-gray-500">(Optional)</span> */}
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
rows="2"
|
||||
value={instance.description}
|
||||
placeholder="E.g. Found this issue at 3:29secs"
|
||||
onChange={this.write}
|
||||
className="text-area"
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
<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 connect(
|
||||
(state) => ({
|
||||
creating: state.getIn(['assignments', 'addActivity', 'loading']),
|
||||
projects: state.getIn(['assignments', 'projects']),
|
||||
projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']),
|
||||
users: state.getIn(['assignments', 'users']),
|
||||
instance: state.getIn(['assignments', 'instance']),
|
||||
metaLoading: state.getIn(['assignments', 'fetchMeta', 'loading']),
|
||||
issueTypes: state.getIn(['assignments', 'issueTypes']),
|
||||
errors: state.getIn(['assignments', 'addActivity', 'errors']),
|
||||
}),
|
||||
{ addActivity, init, edit, fetchAssignments, fetchMeta }
|
||||
)(IssueForm);
|
||||
export default observer(IssueForm);
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
const GotoSessionLink = props => (
|
||||
<a className="flex items-center absolute right-0 mr-3 cursor-pointer">
|
||||
{'Go to session'}
|
||||
<Icon name="next1" size="16" />
|
||||
</a>
|
||||
)
|
||||
|
||||
const IssueHeader = ({issue, typeIcon, assignee}) => {
|
||||
return (
|
||||
<div className="relative p-6 bg-white">
|
||||
{/* <GotoSessionLink /> */}
|
||||
{/* <ActiveIssueClose /> */}
|
||||
<div className="flex leading-none mb-2 items-center">
|
||||
{ typeIcon }
|
||||
{/* <img className="mr-2" src={typeIcon} alt="" width={16} height={16} /> */}
|
||||
<span className="mr-2 font-medium">{ issue.id }</span>
|
||||
{/* <div className="text-gray-700 text-sm">{ '@ 00:13 Secs'}</div> */}
|
||||
{ assignee &&
|
||||
<div>
|
||||
<span className="text-gray-600 mr-2">{'Assigned to'}</span>
|
||||
<span className="font-medium" key={ assignee.id }>{ assignee.name }</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<h2 className="text-xl font-medium mb-2 truncate">{issue.title}</h2>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IssueHeader;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Tooltip } from 'UI';
|
||||
import stl from './issueListItem.module.css';
|
||||
|
||||
const IssueListItem = ({ issue, onClick, icon, user, active }) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(issue)}
|
||||
className={cn(
|
||||
stl.wrapper,
|
||||
active ? 'active-bg' : '',
|
||||
'flex flex-col justify-between cursor-pointer text-base text-gray-800'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
{icon}
|
||||
<span>{issue.id}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{user && (
|
||||
<Tooltip title={'Assignee ' + user.name}>
|
||||
<img src={user.avatarUrls['24x24']} width="24" height="24" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={stl.title}>{issue.title}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IssueListItem;
|
||||
|
|
@ -1,81 +1,56 @@
|
|||
import { Button, Tooltip } from 'antd';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Icon, Popover } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { Popover, Icon } from 'UI';
|
||||
import IssuesModal from './IssuesModal';
|
||||
import { fetchProjects, fetchMeta } from 'Duck/assignments';
|
||||
import { Tooltip, Button } from 'antd';
|
||||
|
||||
@connect(
|
||||
(state) => ({
|
||||
issues: state.getIn(['assignments', 'list']),
|
||||
metaLoading: state.getIn(['assignments', 'fetchMeta', 'loading']),
|
||||
projects: state.getIn(['assignments', 'projects']),
|
||||
projectsFetched: state.getIn(['assignments', 'projectsFetched']),
|
||||
activeIssue: state.getIn(['assignments', 'activeIssue']),
|
||||
fetchIssueLoading: state.getIn(['assignments', 'fetchAssignment', 'loading']),
|
||||
fetchIssuesLoading: state.getIn(['assignments', 'fetchAssignments', 'loading']),
|
||||
projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']),
|
||||
issuesIntegration: state.getIn(['issues', 'list']) || {},
|
||||
issuesFetched: state.getIn(['issues', 'issuesFetched']),
|
||||
}),
|
||||
{ fetchMeta, fetchProjects }
|
||||
)
|
||||
class Issues extends React.Component {
|
||||
state = { showModal: false };
|
||||
function Issues(props) {
|
||||
const { issueReportingStore } = useStore();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { showModal: false };
|
||||
}
|
||||
|
||||
closeModal = () => {
|
||||
this.setState({ showModal: false });
|
||||
};
|
||||
|
||||
showIssuesList = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ showModal: true });
|
||||
};
|
||||
|
||||
handleOpen = () => {
|
||||
this.setState({ showModal: true });
|
||||
if (!this.props.projectsFetched) {
|
||||
// cache projects fetch
|
||||
this.props.fetchProjects().then(
|
||||
function () {
|
||||
const { projects } = this.props;
|
||||
if (projects && projects.first()) {
|
||||
this.props.fetchMeta(projects.first().id);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
const handleOpen = () => {
|
||||
issueReportingStore.init();
|
||||
if (!issueReportingStore.projectsFetched) {
|
||||
issueReportingStore.fetchProjects().then((projects) => {
|
||||
if (projects && projects[0]) {
|
||||
void issueReportingStore.fetchMeta(projects[0].id);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { sessionId, issuesIntegration } = this.props;
|
||||
const provider = issuesIntegration.first()?.provider || '';
|
||||
const { sessionId, issuesIntegration } = props;
|
||||
const provider = issuesIntegration[0]?.provider || '';
|
||||
|
||||
return (
|
||||
<Popover
|
||||
onOpen={this.handleOpen}
|
||||
render={({ close }) => (
|
||||
<div>
|
||||
<IssuesModal provider={provider} sessionId={sessionId} closeHandler={close} />
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
console.log(provider, sessionId, issuesIntegration)
|
||||
return (
|
||||
<Popover
|
||||
onOpen={handleOpen}
|
||||
render={({ close }) => (
|
||||
<div>
|
||||
<Tooltip title={'Create Issue'} placement='bottom'>
|
||||
<Button size={'small'} className={'flex items-center justify-center'}>
|
||||
<Icon name={`integrations/${provider === 'jira' ? 'jira' : 'github'}`} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<IssuesModal
|
||||
provider={provider}
|
||||
sessionId={sessionId}
|
||||
closeHandler={close}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Tooltip title={'Create Issue'} placement="bottom">
|
||||
<Button size={'small'} className={'flex items-center justify-center'}>
|
||||
<Icon
|
||||
name={`integrations/${provider === 'jira' ? 'jira' : 'github'}`}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default Issues;
|
||||
export default connect((state) => ({
|
||||
issuesIntegration: state.getIn(['issues', 'list']) || {},
|
||||
issuesFetched: state.getIn(['issues', 'issuesFetched']),
|
||||
}))(observer(Issues));
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ import store from 'App/store';
|
|||
const IssuesModal = ({
|
||||
sessionId,
|
||||
closeHandler,
|
||||
provider
|
||||
}) => {
|
||||
return (
|
||||
<div className={ stl.wrapper }>
|
||||
<h3 className="text-xl font-semibold">
|
||||
{/* <Icon name={headerIcon} size="18" color="color-gray-darkest" /> */}
|
||||
<span>Create Issue</span>
|
||||
</h3>
|
||||
<Provider store={store}>
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
|
||||
.codeMirror > div {
|
||||
border: 1px solid #eee;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.para {
|
||||
padding: 3px 0;
|
||||
}
|
||||
.mention {
|
||||
font-weight: 500;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.quote {
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
border-left: solid 2px $gray-light;
|
||||
color: $gray-dark;
|
||||
margin: 5px 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: lightgray;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.rule {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
|
@ -1,2 +1 @@
|
|||
export { defualt as Issues } from './Issues';
|
||||
export { defualt as IssueModal } from './IssuesModal';
|
||||
export { defualt as Issues } from './Issues';
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.activitiesList {
|
||||
max-height: calc(100vh - 186px);
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
|
||||
.title {
|
||||
max-width: 90%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
transition: all 0.4s;
|
||||
padding: 8px 14px;
|
||||
/* margin: 0 -14px; */
|
||||
height: 70px;
|
||||
background-color: white;
|
||||
border-bottom: solid thin $gray-light;
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import { List } from 'immutable';
|
||||
import IssuesModal from './IssuesModal';
|
||||
import IssueHeader from './IssueHeader';
|
||||
import IssueComment from './IssueComment';
|
||||
import IssueCommentForm from './IssueCommentForm';
|
||||
import IssueDetails from './IssueDetails';
|
||||
import IssueForm from './IssueForm';
|
||||
import IssueListItem from './IssueListItem';
|
||||
import IssueDescription from './IssueDescription';
|
||||
|
||||
const description = {
|
||||
"version": 1,
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "test para"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "blockquote",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "test quote"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "rule"
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "another para"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": []
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": []
|
||||
},
|
||||
{
|
||||
"type": "codeBlock",
|
||||
"attrs": {},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "var d = \"test code\"\nvar e = \"test new line\""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": []
|
||||
}
|
||||
]
|
||||
}
|
||||
const issueTypeIcons = {
|
||||
'1': 'https://openreplay.atlassian.net/secure/viewavatar?size=medium&avatarId=10310&avatarType=issuetype',
|
||||
}
|
||||
const issueTypes = [
|
||||
{
|
||||
id: 1,
|
||||
iconUrl: 'https://openreplay.atlassian.net/secure/viewavatar?size=medium&avatarId=10310&avatarType=issuetype',
|
||||
name: 'Improvement'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
iconUrl: 'https://openreplay.atlassian.net/secure/viewavatar?size=medium&avatarId=10310&avatarType=issuetype',
|
||||
name: 'Bug'
|
||||
}
|
||||
]
|
||||
const user = {
|
||||
id: 1,
|
||||
name: 'test',
|
||||
avatarUrls: {
|
||||
"16x16": "https://secure.gravatar.com/avatar/900294aa68b33490b16615b57e9709fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMO-3.png&size=16&s=16",
|
||||
"24x24": "https://secure.gravatar.com/avatar/900294aa68b33490b16615b57e9709fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMO-3.png&size=24&s=24"
|
||||
}
|
||||
}
|
||||
const activities = [
|
||||
{
|
||||
id: 1,
|
||||
message: {
|
||||
"version": 1,
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "First Para"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Second para"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Third para",
|
||||
"marks": [
|
||||
{
|
||||
"type": "strong"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "blockquote",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Quote"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": []
|
||||
},
|
||||
{
|
||||
"type": "codeBlock",
|
||||
"attrs": {},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "alert('test')\nvar dd = \"Another line\""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "rule"
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "mention",
|
||||
"attrs": {
|
||||
"id": "5d8398868a50e80c2feed3f6",
|
||||
"text": "Someone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": " "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": []
|
||||
},
|
||||
{
|
||||
"type": "codeBlock",
|
||||
"attrs": {
|
||||
"language": "javascript"
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "var d = \"test\""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": []
|
||||
}
|
||||
]
|
||||
},
|
||||
author: 1,
|
||||
user: user
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
message: {
|
||||
"version": 1,
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "happy debugging"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
author: 1,
|
||||
user: user
|
||||
}
|
||||
]
|
||||
const issues = [
|
||||
{
|
||||
title: 'Crash Report - runtime error: index out of range',
|
||||
description: description,
|
||||
commentsCount: 4,
|
||||
activities: List(activities),
|
||||
assignee: user.id,
|
||||
issueType: 1,
|
||||
id: 'APP-222'
|
||||
},
|
||||
{
|
||||
title: 'this is the second one',
|
||||
description: description,
|
||||
commentsCount: 10,
|
||||
activities: activities,
|
||||
assignee: user.id,
|
||||
issueType: 1,
|
||||
id: 'APP-333'
|
||||
},
|
||||
{
|
||||
title: 'this is the third one',
|
||||
description: description,
|
||||
commentsCount: 0,
|
||||
activities: activities,
|
||||
assignee: user.id,
|
||||
issueType: 1,
|
||||
id: 'APP-444'
|
||||
}
|
||||
];
|
||||
|
||||
const onIssueClick = (issue) => {
|
||||
console.log(issue);
|
||||
}
|
||||
|
||||
storiesOf('Issues', module)
|
||||
.add('IssuesModal', () => (
|
||||
<IssuesModal issues={ issues } />
|
||||
))
|
||||
.add('IssueHeader', () => (
|
||||
<IssueHeader issue={ issues[0] } typeIcon={ issueTypeIcons[1] }/>
|
||||
))
|
||||
.add('IssueComment', () => (
|
||||
<div className="p-4">
|
||||
<IssueComment activity={ activities[0] } />
|
||||
</div>
|
||||
))
|
||||
.add('IssueDescription', () => (
|
||||
<div className="p-4 bg-gray-100">
|
||||
<IssueDescription description={ description } />
|
||||
</div>
|
||||
))
|
||||
.add('IssueCommentForm', () => (
|
||||
<IssueCommentForm />
|
||||
))
|
||||
.add('IssueDetails', () => (
|
||||
<IssueDetails
|
||||
sessionId={ 1}
|
||||
issue={ issues[0] }
|
||||
users={ List([user]) }
|
||||
issueTypeIcons={ issueTypeIcons }
|
||||
/>
|
||||
))
|
||||
.add('IssueListItem', () => (
|
||||
<IssueListItem issue={ issues[0] } icon={ issueTypeIcons[1] } user={ user } />
|
||||
))
|
||||
.add('IssueForm', () => (
|
||||
<div className="p-4">
|
||||
<IssueForm issueTypes={ List(issueTypes) } />
|
||||
</div>
|
||||
))
|
||||
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
import { List, Map, Set } from 'immutable';
|
||||
import Assignment from 'Types/session/assignment';
|
||||
import Activity from 'Types/session/activity';
|
||||
import withRequestState, { RequestTypes } from './requestStateCreator';
|
||||
import { createListUpdater } from './funcTools/tools';
|
||||
import { editType, initType } from './funcTools/crud/types';
|
||||
import { createInit, createEdit } from './funcTools/crud';
|
||||
|
||||
const idKey = 'id';
|
||||
const name = 'assignment';
|
||||
const listUpdater = createListUpdater(idKey);
|
||||
|
||||
const FETCH_PROJECTS = new RequestTypes('asignment/FETCH_PROJECTS');
|
||||
const FETCH_META = new RequestTypes('asignment/FETCH_META');
|
||||
const FETCH_ASSIGNMENTS = new RequestTypes('asignment/FETCH_ASSIGNMENTS');
|
||||
const FETCH_ASSIGNMENT = new RequestTypes('asignment/FETCH_ASSIGNMENT');
|
||||
const ADD_ACTIVITY = new RequestTypes('asignment/ADD_ACTIVITY');
|
||||
const ADD_MESSAGE = new RequestTypes('asignment/ADD_MESSAGE');
|
||||
const EDIT = editType(name);
|
||||
const INIT = initType(name);
|
||||
|
||||
const initialState = Map({
|
||||
list: List(),
|
||||
instance: new Assignment(),
|
||||
activeIssue: new Assignment(),
|
||||
issueTypes: List(),
|
||||
issueTypeIcons: Set(),
|
||||
users: List(),
|
||||
projects: List(),
|
||||
projectsFetched: false
|
||||
});
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
const users = state.get('users');
|
||||
let issueTypes = []
|
||||
switch (action.type) {
|
||||
case INIT:
|
||||
action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : '';
|
||||
return state.set('instance', new Assignment(action.instance));
|
||||
case EDIT:
|
||||
const inst = state.get('instance')
|
||||
return state.set('instance', new Assignment({ ...inst, ...action.instance }));
|
||||
case FETCH_PROJECTS.SUCCESS:
|
||||
return state.set('projects', List(action.data)).set('projectsFetched', true);
|
||||
case FETCH_ASSIGNMENTS.SUCCESS:
|
||||
return state.set('list', List(action.data).map(as => new Assignment(as)));
|
||||
case FETCH_ASSIGNMENT.SUCCESS:
|
||||
return state.set('activeIssue', new Assignment({ ...action.data, users}));
|
||||
case FETCH_META.SUCCESS:
|
||||
issueTypes = action.data.issueTypes
|
||||
const issueTypeIcons = {}
|
||||
issueTypes.forEach(iss => {
|
||||
issueTypeIcons[iss.id] = iss.iconUrl
|
||||
})
|
||||
return state.set('issueTypes', issueTypes)
|
||||
.set('users', List(action.data.users))
|
||||
.set('issueTypeIcons', issueTypeIcons)
|
||||
case ADD_ACTIVITY.SUCCESS:
|
||||
const instance = new Assignment(action.data);
|
||||
return listUpdater(state, instance);
|
||||
case ADD_MESSAGE.SUCCESS:
|
||||
const user = users.filter(user => user.id === action.data.author).first();
|
||||
const activity = new Activity({ type: 'message', user, ...action.data,});
|
||||
return state.update([ 'activeIssue' ], issue => issue.activities.push(activity));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default withRequestState({
|
||||
fetchProjects: FETCH_PROJECTS,
|
||||
fetchMeta: FETCH_META,
|
||||
fetchAssignments: FETCH_ASSIGNMENTS,
|
||||
addActivity: ADD_ACTIVITY,
|
||||
fetchAssignment: FETCH_ASSIGNMENT,
|
||||
addMessage: ADD_MESSAGE
|
||||
}, reducer);
|
||||
|
||||
export const init = createInit(name);
|
||||
export const edit = createEdit(name);
|
||||
|
||||
export function fetchProjects() {
|
||||
return {
|
||||
types: FETCH_PROJECTS.toArray(),
|
||||
call: client => client.get(`/integrations/issues/list_projects`)
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMeta(projectId) {
|
||||
return {
|
||||
types: FETCH_META.toArray(),
|
||||
call: client => client.get(`/integrations/issues/${projectId}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchAssignments(sessionId) {
|
||||
return {
|
||||
types: FETCH_ASSIGNMENTS.toArray(),
|
||||
call: client => client.get(`/sessions/${ sessionId }/assign`)
|
||||
}
|
||||
}
|
||||
|
||||
export function addActivity(sessionId, params) {
|
||||
const data = { ...params, assignee: params.assignee, issueType: params.issueType }
|
||||
return {
|
||||
types: ADD_ACTIVITY.toArray(),
|
||||
call: client => client.post(`/sessions/${ sessionId }/assign/projects/${params.projectId}`, data),
|
||||
}
|
||||
}
|
||||
|
||||
export function addMessage(sessionId, assignmentId, params) {
|
||||
return {
|
||||
types: ADD_MESSAGE.toArray(),
|
||||
call: client => client.post(`/sessions/${ sessionId }/assign/${ assignmentId }/comment`, params),
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ import { combineReducers } from 'redux-immutable';
|
|||
|
||||
import user from './user';
|
||||
import sessions from './sessions';
|
||||
import assignments from './assignments';
|
||||
import filters from './filters';
|
||||
import funnelFilters from './funnelFilters';
|
||||
import sources from './sources';
|
||||
|
|
@ -19,7 +18,6 @@ import liveSearch from './liveSearch';
|
|||
const rootReducer = combineReducers({
|
||||
user,
|
||||
sessions,
|
||||
assignments,
|
||||
filters,
|
||||
funnelFilters,
|
||||
site,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import Assignment from 'Types/session/assignment';
|
||||
import ReportedIssue from 'Types/session/assignment';
|
||||
import Activity from 'Types/session/activity';
|
||||
import { List, Map, Set } from 'immutable';
|
||||
import withRequestState, { RequestTypes } from 'Duck/requestStateCreator';
|
||||
|
|
@ -22,8 +22,8 @@ const RESET_ACTIVE_ISSUE = 'assignment/RESET_ACTIVE_ISSUE';
|
|||
|
||||
const initialState = Map({
|
||||
list: List(),
|
||||
instance: new Assignment(),
|
||||
activeIssue: new Assignment(),
|
||||
instance: new ReportedIssue(),
|
||||
activeIssue: new ReportedIssue(),
|
||||
issueTypes: List(),
|
||||
issueTypeIcons: Set(),
|
||||
users: List(),
|
||||
|
|
@ -38,9 +38,9 @@ const reducer = (state = initialState, action = {}) => {
|
|||
case FETCH_PROJECTS.SUCCESS:
|
||||
return state.set('projects', List(action.data));
|
||||
case FETCH_ASSIGNMENTS.SUCCESS:
|
||||
return state.set('list', action.data.map(as => new Assignment(as)));
|
||||
return state.set('list', action.data.map(as => new ReportedIssue(as)));
|
||||
case ADD_ACTIVITY.SUCCESS:
|
||||
const instance = new Assignment(action.data);
|
||||
const instance = new ReportedIssue(action.data);
|
||||
return listUpdater(state, instance);
|
||||
case FETCH_META.SUCCESS:
|
||||
issueTypes = action.data.issueTypes;
|
||||
|
|
@ -52,16 +52,16 @@ const reducer = (state = initialState, action = {}) => {
|
|||
.set('users', List(action.data.users))
|
||||
.set('issueTypeIcons', issueTypeIcons)
|
||||
case FETCH_ISSUE.SUCCESS:
|
||||
return state.set('activeIssue', new Assignment({ ...action.data, users}));
|
||||
return state.set('activeIssue', new ReportedIssue({ ...action.data, users}));
|
||||
case RESET_ACTIVE_ISSUE:
|
||||
return state.set('activeIssue', new Assignment());
|
||||
return state.set('activeIssue', new ReportedIssue());
|
||||
case ADD_MESSAGE.SUCCESS:
|
||||
const user = users.filter(user => user.id === action.data.author).first();
|
||||
const activity = new Activity({ type: 'message', user, ...action.data,});
|
||||
return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity));
|
||||
case INIT:
|
||||
action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : '';
|
||||
return state.set('instance', new Assignment(action.instance));
|
||||
return state.set('instance', new ReportedIssue(action.instance));
|
||||
case EDIT:
|
||||
return state.mergeIn([ 'instance' ], action.instance);
|
||||
default:
|
||||
|
|
@ -80,13 +80,6 @@ export default withRequestState({
|
|||
export const init = createInit(name);
|
||||
export const edit = createEdit(name);
|
||||
|
||||
export function fetchAssignments(sessionId) {
|
||||
return {
|
||||
types: FETCH_ASSIGNMENTS.toArray(),
|
||||
call: client => client.get(`/sessions/${ sessionId }/assign`)
|
||||
}
|
||||
}
|
||||
|
||||
export function resetActiveIsue() {
|
||||
return {
|
||||
type: RESET_ACTIVE_ISSUE
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import SpotStore from "./spotStore";
|
|||
import LoginStore from "./loginStore";
|
||||
import FilterStore from "./filterStore";
|
||||
import UiPlayerStore from './uiPlayerStore';
|
||||
import IssueReportingStore from './issueReportingStore';
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: DashboardStore;
|
||||
|
|
@ -51,6 +52,7 @@ export class RootStore {
|
|||
loginStore: LoginStore;
|
||||
filterStore: FilterStore;
|
||||
uiPlayerStore: UiPlayerStore;
|
||||
issueReportingStore: IssueReportingStore;
|
||||
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
|
|
@ -77,6 +79,7 @@ export class RootStore {
|
|||
this.loginStore = new LoginStore();
|
||||
this.filterStore = new FilterStore();
|
||||
this.uiPlayerStore = new UiPlayerStore();
|
||||
this.issueReportingStore = new IssueReportingStore();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
|
|
|
|||
90
frontend/app/mstore/issueReportingStore.ts
Normal file
90
frontend/app/mstore/issueReportingStore.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { makeAutoObservable } from 'mobx';
|
||||
import ReportedIssue from "../types/session/assignment";
|
||||
import { issueReportsService } from "App/services";
|
||||
|
||||
export default class issueReportingStore {
|
||||
instance: ReportedIssue
|
||||
issueTypes: any[] = []
|
||||
issueTypeIcons: {}
|
||||
users: any[] = []
|
||||
projects: any[] = []
|
||||
projectsFetched = false
|
||||
projectsLoading = false
|
||||
metaLoading = false
|
||||
createLoading = false
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
init = (instance: any) => {
|
||||
this.instance = new ReportedIssue(instance);
|
||||
if (this.issueTypes.length > 0) {
|
||||
this.instance.issueType = this.issueTypes[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
editInstance = (data: any) => {
|
||||
const inst = this.instance
|
||||
this.instance = new ReportedIssue({ ...inst, ...data })
|
||||
}
|
||||
|
||||
setProjects = (projects: any[]) => {
|
||||
this.projectsFetched = true;
|
||||
this.projects = projects;
|
||||
}
|
||||
|
||||
setMeta = (data: any) => {
|
||||
const issueTypes = data.issueTypes || [];
|
||||
const itIcons = {}
|
||||
issueTypes.forEach((it: any) => {
|
||||
itIcons[it.id] = it.iconUrl
|
||||
})
|
||||
|
||||
this.issueTypes = issueTypes;
|
||||
this.issueTypeIcons = itIcons;
|
||||
this.users = data.users || [];
|
||||
}
|
||||
|
||||
fetchProjects = async () => {
|
||||
if (this.projectsLoading) return;
|
||||
this.projectsLoading = true;
|
||||
try {
|
||||
const { data } = await issueReportsService.fetchProjects();
|
||||
this.setProjects(data);
|
||||
this.projectsFetched = true;
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.projectsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
fetchMeta = async (projectId: number) => {
|
||||
if (this.metaLoading) return;
|
||||
this.metaLoading = true;
|
||||
try {
|
||||
const { data } = await issueReportsService.fetchMeta(projectId);
|
||||
this.setMeta(data);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.metaLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
saveIssue = async (sessionId: string, params: any) => {
|
||||
if (this.createLoading) return;
|
||||
this.createLoading = true;
|
||||
try {
|
||||
const data = { ...params, assignee: params.assignee, issueType: params.issueType }
|
||||
const { data: issue } = await issueReportsService.saveIssue(sessionId, data);
|
||||
this.init(issue)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.createLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
frontend/app/services/IssueReportsService.ts
Normal file
21
frontend/app/services/IssueReportsService.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import BaseService from 'App/services/BaseService';
|
||||
|
||||
export default class IssueReportsService extends BaseService {
|
||||
fetchProjects = async () => {
|
||||
const r = await this.client.get(`/integrations/issues/list_projects`)
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
|
||||
fetchMeta = async (projectId: number) => {
|
||||
const r = await this.client.get(`/integrations/issues/${projectId}`)
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
|
||||
saveIssue = async (sessionId: string, data: any) => {
|
||||
const r = await this.client.post(`/sessions/${ sessionId }/assign/projects/${data.projectId}`, data)
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import WebhookService from './WebhookService';
|
|||
import SpotService from './spotService';
|
||||
import LoginService from "./loginService";
|
||||
import FilterService from "./FilterService";
|
||||
import IssueReportsService from "./IssueReportsService";
|
||||
|
||||
export const dashboardService = new DashboardService();
|
||||
export const metricService = new MetricService();
|
||||
|
|
@ -42,6 +43,7 @@ export const aiService = new AiService();
|
|||
export const spotService = new SpotService();
|
||||
export const loginService = new LoginService();
|
||||
export const filterService = new FilterService();
|
||||
export const issueReportsService = new IssueReportsService();
|
||||
|
||||
export const services = [
|
||||
dashboardService,
|
||||
|
|
@ -65,4 +67,5 @@ export const services = [
|
|||
spotService,
|
||||
loginService,
|
||||
filterService,
|
||||
issueReportsService,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import Activity, { IActivity } from './activity';
|
|||
import { DateTime } from 'luxon';
|
||||
import { notEmptyString } from 'App/validate';
|
||||
|
||||
interface IAssignment {
|
||||
interface IReportedIssue {
|
||||
id: string;
|
||||
title: string;
|
||||
timestamp: number;
|
||||
creatorId: string;
|
||||
sessionId: string;
|
||||
projectId: string;
|
||||
siteId: string;
|
||||
|
|
@ -22,23 +21,21 @@ interface IAssignment {
|
|||
users: { id: string }[]
|
||||
}
|
||||
|
||||
export default class Assignment {
|
||||
id: IAssignment["id"];
|
||||
title: IAssignment["title"] = '';
|
||||
timestamp: IAssignment["timestamp"];
|
||||
creatorId: IAssignment["creatorId"];
|
||||
sessionId: IAssignment["sessionId"];
|
||||
projectId: IAssignment["projectId"] = '';
|
||||
siteId: IAssignment["siteId"];
|
||||
activities: IAssignment["activities"];
|
||||
closed: IAssignment["closed"];
|
||||
assignee: IAssignment["assignee"] = '';
|
||||
commentsCount: IAssignment["commentsCount"];
|
||||
issueType: IAssignment["issueType"] = '';
|
||||
description: IAssignment["description"] = '';
|
||||
iconUrl: IAssignment["iconUrl"] = '';
|
||||
export default class ReportedIssue {
|
||||
id: IReportedIssue["id"];
|
||||
title: IReportedIssue["title"] = '';
|
||||
timestamp: IReportedIssue["timestamp"];
|
||||
sessionId: IReportedIssue["sessionId"];
|
||||
projectId: IReportedIssue["projectId"] = '';
|
||||
siteId: IReportedIssue["siteId"];
|
||||
activities: IReportedIssue["activities"];
|
||||
closed: IReportedIssue["closed"];
|
||||
assignee: IReportedIssue["assignee"] = '';
|
||||
issueType: IReportedIssue["issueType"] = '';
|
||||
description: IReportedIssue["description"] = '';
|
||||
iconUrl: IReportedIssue["iconUrl"] = '';
|
||||
|
||||
constructor(assignment?: IAssignment) {
|
||||
constructor(assignment?: IReportedIssue) {
|
||||
if (assignment) {
|
||||
Object.assign(this, {
|
||||
...assignment,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue