aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/reportManager/ReportManager.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-07-19 19:11:06 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-07-19 19:11:06 -0400
commitea217200f1c42e4d4b142abc9abd55ca49535c49 (patch)
tree3f8974dfdc0f63ea0549d7681b74c5b08bcfc1d3 /src/client/util/reportManager/ReportManager.tsx
parent77b26f2dbdc2f3df0ab65aa3053854b4a24c586f (diff)
lots of changes, just need server endpoint before pull
Diffstat (limited to 'src/client/util/reportManager/ReportManager.tsx')
-rw-r--r--src/client/util/reportManager/ReportManager.tsx382
1 files changed, 91 insertions, 291 deletions
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index be46ba0a8..f20c2baaa 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -12,21 +12,17 @@ import { HiOutlineArrowLeft } from 'react-icons/hi';
import { Issue } from './reportManagerSchema';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
-import { Networking } from '../../Network';
import { MainViewModal } from '../../views/MainViewModal';
import { Octokit } from '@octokit/core';
-import { Button, IconButton, OrientationType, Type } from 'browndash-components';
-import { BugType, FileData, Priority, ViewState, darkColors, isLightText, lightColors } from './reportManagerUtils';
-import { IssueCard, IssueView, Tag } from './ReportManagerComponents';
+import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
+import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
+import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents';
import { StrCast } from '../../../fields/Types';
+import { MdRefresh } from 'react-icons/md';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-// StrCast(Doc.UserDoc().userColor);
-// StrCast(Doc.UserDoc().userBackgroundColor);
-// StrCast(Doc.UserDoc().userVariantColor);
-
/**
* Class for reporting and viewing Github issues within the app.
*/
@@ -85,44 +81,28 @@ export class ReportManager extends React.Component<{}> {
});
// Form state
-
- @observable private bugTitle = '';
- @action setBugTitle = action((title: string) => {
- this.bugTitle = title;
- });
- @observable private bugDescription = '';
- @action setBugDescription = action((description: string) => {
- this.bugDescription = description;
+ @observable private formData: ReportForm = emptyReportForm;
+ @action setFormData = action((newData: ReportForm) => {
+ this.formData = newData;
});
- @observable private bugType = '';
- @action setBugType = action((type: string) => {
- this.bugType = type;
- });
- @observable private bugPriority = '';
- @action setBugPriority = action((priortiy: string) => {
- this.bugPriority = priortiy;
- });
-
- @observable private mediaFiles: FileData[] = [];
- @action private setMediaFiles = (files: FileData[]) => {
- this.mediaFiles = files;
- };
public close = action(() => (this.isOpen = false));
public open = action(async () => {
this.isOpen = true;
if (this.shownIssues.length === 0) {
- this.setFetchingIssues(true);
- try {
- // load in the issues if not already loaded
- const issues = (await this.getAllIssues()) as Issue[];
- // filtering to include only open issues and exclude pull requests, maybe add a separate tab for pr's?
- this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
- } catch (err) {
- console.log(err);
- }
- this.setFetchingIssues(false);
+ this.updateIssues();
+ }
+ });
+
+ @action updateIssues = action(async () => {
+ this.setFetchingIssues(true);
+ try {
+ const issues = (await getAllIssues(this.octokit)) as Issue[];
+ this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
+ } catch (err) {
+ console.log(err);
}
+ this.setFetchingIssues(false);
});
constructor(props: {}) {
@@ -136,168 +116,53 @@ export class ReportManager extends React.Component<{}> {
}
/**
- * Fethches issues from Github.
- * @returns array of all issues
- */
- public async getAllIssues(): Promise<any[]> {
- const res = await this.octokit.request('GET /repos/{owner}/{repo}/issues', {
- owner: 'brown-dash',
- repo: 'Dash-Web',
- per_page: 80,
- });
-
- // 200 status means success
- if (res.status === 200) {
- return res.data;
- } else {
- throw new Error('Error getting issues');
- }
- }
-
- /**
* Sends a request to Github to report a new issue with the form data.
* @returns nothing
*/
public async reportIssue(): Promise<void> {
- if (this.bugTitle === '' || this.bugDescription === '' || this.bugType === '' || this.bugPriority === '') {
+ if (this.formData.title === '' || this.formData.description === '') {
alert('Please fill out all required fields to report an issue.');
return;
}
+ let formattedLinks: string[] = [];
this.setSubmitting(true);
-
- const links = await this.uploadFilesToServer();
- console.log(links);
- if (!links) {
- // error uploading files to the server
- return;
+ if (this.formData.mediaFiles.length > 0) {
+ const links = await uploadFilesToServer(this.formData.mediaFiles);
+ console.log(links);
+ if (!links) {
+ return;
+ }
+ formattedLinks = links;
}
- const formattedLinks = (links ?? []).map(this.fileLinktoServerLink);
- // const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
- // owner: 'brown-dash',
- // repo: 'Dash-Web',
- // title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
- // body: `${this.bugDescription} ${formattedLinks.length > 0 && `\n\nFiles:\n${formattedLinks.join('\n')}`}`,
- // labels: ['from-dash-app', this.bugType, this.bugPriority],
- // });
+ const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
+ owner: 'brown-dash',
+ repo: 'Dash-Web',
+ title: formatTitle(this.formData.title, Doc.CurrentUserEmail),
+ body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
+ labels: ['from-dash-app', this.formData.type, this.formData.priority],
+ });
- // // 201 status means success
- // if (req.status !== 201) {
- // alert('Error creating issue on github.');
- // return;
- // }
+ // 201 status means success
+ if (req.status !== 201) {
+ alert('Error creating issue on github.');
+ return;
+ }
// Reset fields
- this.setBugTitle('');
- this.setBugDescription('');
- this.setMediaFiles([]);
- this.setBugType('');
- this.setBugPriority('');
+ this.setFormData(emptyReportForm);
this.setSubmitting(false);
- this.setFetchingIssues(true);
- try {
- // load in the issues if not already loaded
- const issues = (await this.getAllIssues()) as Issue[];
- // filtering to include only open issues and exclude pull requests, maybe add a separate tab for pr's?
- this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
- } catch (err) {
- console.log(err);
- }
- this.setFetchingIssues(false);
+ await this.updateIssues();
alert('Successfully submitted issue.');
}
/**
- * Formats issue title.
- *
- * @param title title of issue
- * @param userEmail email of issue submitter
- * @returns formatted title
- */
- private formatTitle = (title: string, userEmail: string): string => `${title} - ${userEmail.replace('@brown.edu', '')}`;
-
- // turns an upload link -> server link
- // ex:
- // C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
- // -> https://browndash.com/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
- private fileLinktoServerLink = (fileLink: string) => {
- const serverUrl = 'https://browndash.com/';
-
- const regex = 'public';
- const publicIndex = fileLink.indexOf(regex) + regex.length;
-
- const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
- return finalUrl;
- };
-
- /**
- * Gets the server file path.
- *
- * @param link response from file upload
- * @returns server file path
- */
- private getServerPath = (link: any): string => {
- return link.result.accessPaths.agnostic.server as string;
- };
-
- /**
- * Uploads media files to the server.
- * @returns the server paths or undefined on error
- */
- private uploadFilesToServer = async (): Promise<string[] | undefined> => {
- try {
- // need to always upload to browndash
- const links = await Networking.UploadFilesToServer(
- this.mediaFiles.map(file => ({ file: file.file })),
- true
- );
- return (links ?? []).map(this.getServerPath);
- } catch (err) {
- if (err instanceof Error) {
- alert(err.message);
- } else {
- alert(err);
- }
- }
- };
-
- /**
* Handles file upload.
*
* @param files uploaded files
*/
private onDrop = (files: File[]) => {
- this.setMediaFiles([...this.mediaFiles, ...files.map(file => ({ _id: v4(), file }))]);
- };
-
- /**
- * Returns when the issue passes the current filters.
- *
- * @param issue issue to check
- * @returns boolean indicating whether the issue passes the current filters
- */
- private passesTagFilter = (issue: Issue) => {
- let passesPriority = true;
- let passesBug = true;
- if (this.priorityFilter) {
- passesPriority = issue.labels.some(label => {
- if (typeof label === 'string') {
- return label === this.priorityFilter;
- } else {
- return label.name === this.priorityFilter;
- }
- });
- }
- if (this.bugFilter) {
- passesBug = issue.labels.some(label => {
- if (typeof label === 'string') {
- return label === this.bugFilter;
- } else {
- return label.name === this.bugFilter;
- }
- });
- }
- return passesPriority && passesBug;
+ this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: v4(), file }))] });
};
/**
@@ -317,7 +182,7 @@ export class ReportManager extends React.Component<{}> {
<img height={100} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
</div>
<div className="close-btn">
- <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} />
+ <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
</div>
</div>
);
@@ -331,7 +196,7 @@ export class ReportManager extends React.Component<{}> {
</video>
</div>
<div className="close-btn">
- <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} />
+ <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
</div>
</div>
);
@@ -340,7 +205,7 @@ export class ReportManager extends React.Component<{}> {
<div key={fileData._id} className="report-audio-wrapper">
<audio src={preview} controls />
<div className="close-btn">
- <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} />
+ <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
</div>
</div>
);
@@ -352,94 +217,31 @@ export class ReportManager extends React.Component<{}> {
* @returns the component that dispays all issues
*/
private viewIssuesComponent = () => {
- const darkMode = isLightText(StrCast(Doc.UserDoc().userBackgroundColor));
+ const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
const colors = darkMode ? darkColors : lightColors;
- const isTagDarkMode = isLightText(StrCast(Doc.UserDoc().userVariantColor));
- const activeTagTextColor = isTagDarkMode ? darkColors.text : lightColors.text;
return (
<div className="view-issues" style={{ backgroundColor: StrCast(Doc.UserDoc().userBackgroundColor), color: colors.text }}>
<div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
<div className="report-header">
<h2 style={{ color: colors.text }}>Open Issues</h2>
- <Button
- type={Type.TERT}
- color={StrCast(Doc.UserDoc().userColor)}
- text="Report Issue"
- onClick={() => {
- this.setViewState(ViewState.CREATE);
- }}
- />
- </div>
- <input
- className="report-input"
- type="text"
- placeholder="Filter by query..."
- onChange={e => {
- this.setQuery(e.target.value);
- }}
- required
- />
- <div className="issues-filters">
- <div className="issues-filter">
- <Tag
- text={'All'}
- onClick={() => {
- this.setPriorityFilter(null);
- }}
- fontSize="12px"
- backgroundColor={this.priorityFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
- color={this.priorityFilter === null ? activeTagTextColor : colors.textGrey}
- border
- borderColor={this.priorityFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : colors.border}
- />
- {Object.values(Priority).map(p => {
- return (
- <Tag
- key={p}
- text={p}
- onClick={() => {
- this.setPriorityFilter(p);
- }}
- fontSize="12px"
- backgroundColor={this.priorityFilter === p ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
- color={this.priorityFilter === p ? activeTagTextColor : colors.textGrey}
- border
- borderColor={this.priorityFilter === p ? StrCast(Doc.UserDoc().userVariantColor) : colors.border}
- />
- );
- })}
- </div>
- <div className="issues-filter">
- <Tag
- text={'All'}
+ <div className="header-btns">
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="refresh" icon={<MdRefresh size="16px" />} onClick={this.updateIssues} />
+ <Button
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ text="Report Issue"
onClick={() => {
- this.setBugFilter(null);
+ this.setViewState(ViewState.CREATE);
}}
- fontSize="12px"
- backgroundColor={this.bugFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
- color={this.bugFilter === null ? activeTagTextColor : colors.textGrey}
- border
- borderColor={this.bugFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : colors.border}
/>
- {Object.values(BugType).map(b => {
- return (
- <Tag
- key={b}
- text={b}
- onClick={() => {
- this.setBugFilter(b);
- }}
- fontSize="12px"
- backgroundColor={this.bugFilter === b ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
- color={this.bugFilter === b ? activeTagTextColor : colors.textGrey}
- border
- borderColor={this.bugFilter === b ? StrCast(Doc.UserDoc().userVariantColor) : colors.border}
- />
- );
- })}
</div>
</div>
+ <FormInput value={this.query} placeholder="Filter by query..." onChange={this.setQuery} />
+ <div className="issues-filters">
+ <Filter items={Object.values(Priority)} activeValue={this.priorityFilter} setActiveValue={p => this.setPriorityFilter(p)} />
+ <Filter items={Object.values(BugType)} activeValue={this.bugFilter} setActiveValue={b => this.setBugFilter(b)} />
+ </div>
<div className="issues">
{this.fetchingIssues ? (
<div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
@@ -448,7 +250,7 @@ export class ReportManager extends React.Component<{}> {
) : (
this.shownIssues
.filter(issue => issue.title.toLowerCase().includes(this.query))
- .filter(issue => this.passesTagFilter(issue))
+ .filter(issue => passesTagFilter(issue, this.priorityFilter, this.bugFilter))
.map(issue => (
<IssueCard
key={issue.number}
@@ -482,7 +284,7 @@ export class ReportManager extends React.Component<{}> {
* @returns the form component for submitting issues
*/
private reportIssueComponent = () => {
- const darkMode = isLightText(StrCast(Doc.UserDoc().userBackgroundColor));
+ const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
const colors = darkMode ? darkColors : lightColors;
return (
@@ -502,38 +304,37 @@ export class ReportManager extends React.Component<{}> {
</div>
<div className="report-section">
<label className="report-label">Please provide a title for the bug</label>
- <input className="report-input" value={this.bugTitle} type="text" placeholder="Title..." onChange={e => this.setBugTitle(e.target.value)} required />
+ <FormInput value={this.formData.title} placeholder="Title..." onChange={val => this.setFormData({ ...this.formData, title: val })} />
</div>
<div className="report-section">
<label className="report-label">Please leave a description for the bug and how it can be recreated</label>
- <textarea className="report-textarea" value={this.bugDescription} placeholder="Description..." onChange={e => this.setBugDescription(e.target.value)} required />
+ <FormTextArea value={this.formData.description} placeholder="Description..." onChange={val => this.setFormData({ ...this.formData, description: val })} />
</div>
<div className="report-selects">
- <select className="report-select" name="bugType" onChange={e => (this.bugType = e.target.value)}>
- <option value="" disabled selected>
- Type
- </option>
- <option className="report-opt" value={BugType.BUG}>
- Bug
- </option>
- <option className="report-opt" value={BugType.COSMETIC}>
- Poor Design or Cosmetic
- </option>
- <option className="report-opt" value={BugType.DOCUMENTATION}>
- Poor Documentation
- </option>
- <option className="report-opt" value={BugType.ENHANCEMENT}>
- New feature or request
- </option>
- </select>
- <select className="report-select" name="priority" onChange={e => (this.bugPriority = e.target.value)}>
- <option className="report-opt" value="" disabled selected>
- Priority
- </option>
- <option value={Priority.LOW}>Low</option>
- <option value={Priority.MEDIUM}>Medium</option>
- <option value={Priority.HIGH}>High</option>
- </select>
+ <Dropdown
+ color={StrCast(Doc.UserDoc().userColor)}
+ formLabel={'Type'}
+ items={bugDropdownItems}
+ selectedVal={this.formData.type}
+ setSelectedVal={val => {
+ if (typeof val === 'string') this.setFormData({ ...this.formData, type: val as BugType });
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ fillWidth
+ />
+ <Dropdown
+ color={StrCast(Doc.UserDoc().userColor)}
+ formLabel={'Priority'}
+ items={priorityDropdownItems}
+ selectedVal={this.formData.priority}
+ setSelectedVal={val => {
+ if (typeof val === 'string') this.setFormData({ ...this.formData, priority: val as Priority });
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ fillWidth
+ />
</div>
<Dropzone
onDrop={this.onDrop}
@@ -545,7 +346,7 @@ export class ReportManager extends React.Component<{}> {
'audio/ogg': ['.ogg'],
}}>
{({ getRootProps, getInputProps }) => (
- <div {...getRootProps({ className: 'dropzone' })}>
+ <div {...getRootProps({ className: 'dropzone' })} style={{ borderColor: isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border }}>
<input {...getInputProps()} />
<div className="dropzone-instructions">
<AiOutlineUpload size={25} />
@@ -554,12 +355,12 @@ export class ReportManager extends React.Component<{}> {
</div>
)}
</Dropzone>
- {this.mediaFiles.length > 0 && <ul className="file-list">{this.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
+ {this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
{this.submitting ? (
<Button
text="Submit"
type={Type.TERT}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
iconPlacement="right"
onClick={() => {
@@ -570,13 +371,12 @@ export class ReportManager extends React.Component<{}> {
<Button
text="Submit"
type={Type.TERT}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
onClick={() => {
this.reportIssue();
}}
/>
)}
-
<div style={{ position: 'absolute', top: '4px', right: '4px' }}>
<IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} />
</div>