aboutsummaryrefslogtreecommitdiffstats
path: root/webui/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'webui/src/pages')
-rw-r--r--webui/src/pages/bug/CommentForm.tsx37
-rw-r--r--webui/src/pages/bug/Message.tsx7
-rw-r--r--webui/src/pages/bug/MessageHistoryDialog.tsx7
-rw-r--r--webui/src/pages/list/BugRow.tsx2
-rw-r--r--webui/src/pages/list/Filter.tsx44
-rw-r--r--webui/src/pages/list/FilterToolbar.tsx13
6 files changed, 74 insertions, 36 deletions
diff --git a/webui/src/pages/bug/CommentForm.tsx b/webui/src/pages/bug/CommentForm.tsx
index a8ce4319..6d917889 100644
--- a/webui/src/pages/bug/CommentForm.tsx
+++ b/webui/src/pages/bug/CommentForm.tsx
@@ -5,8 +5,10 @@ import Paper from '@material-ui/core/Paper';
import { makeStyles, Theme } from '@material-ui/core/styles';
import CommentInput from '../../components/CommentInput/CommentInput';
-import CloseBugButton from 'src/components/CloseBugButton/CloseBugButton';
-import ReopenBugButton from 'src/components/ReopenBugButton/ReopenBugButton';
+import CloseBugButton from 'src/components/CloseBugButton';
+import CloseBugWithCommentButton from 'src/components/CloseBugWithCommentButton';
+import ReopenBugButton from 'src/components/ReopenBugButton';
+import ReopenBugWithCommentButton from 'src/components/ReopenBugWithCommentButton';
import { BugFragment } from './Bug.generated';
import { useAddCommentMutation } from './CommentForm.generated';
@@ -77,12 +79,29 @@ function CommentForm({ bug }: Props) {
if (issueComment.length > 0) submit();
};
- function getCloseButton() {
- return <CloseBugButton bug={bug} disabled={issueComment.length > 0} />;
- }
-
- function getReopenButton() {
- return <ReopenBugButton bug={bug} disabled={issueComment.length > 0} />;
+ function getBugStatusButton() {
+ if (bug.status === 'OPEN' && issueComment.length > 0) {
+ return (
+ <CloseBugWithCommentButton
+ bug={bug}
+ comment={issueComment}
+ postClick={resetForm}
+ />
+ );
+ }
+ if (bug.status === 'OPEN') {
+ return <CloseBugButton bug={bug} />;
+ }
+ if (bug.status === 'CLOSED' && issueComment.length > 0) {
+ return (
+ <ReopenBugWithCommentButton
+ bug={bug}
+ comment={issueComment}
+ postClick={resetForm}
+ />
+ );
+ }
+ return <ReopenBugButton bug={bug} />;
}
return (
@@ -94,7 +113,7 @@ function CommentForm({ bug }: Props) {
onChange={(comment: string) => setIssueComment(comment)}
/>
<div className={classes.actions}>
- {bug.status === 'OPEN' ? getCloseButton() : getReopenButton()}
+ {getBugStatusButton()}
<Button
className={classes.greenButton}
variant="contained"
diff --git a/webui/src/pages/bug/Message.tsx b/webui/src/pages/bug/Message.tsx
index 808bb525..51087faa 100644
--- a/webui/src/pages/bug/Message.tsx
+++ b/webui/src/pages/bug/Message.tsx
@@ -57,6 +57,7 @@ const useStyles = makeStyles((theme) => ({
marginLeft: '0.5rem',
},
body: {
+ overflow: 'auto',
...theme.typography.body2,
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
@@ -156,7 +157,11 @@ function Message({ bug, op }: Props) {
</IfLoggedIn>
</header>
<section className={classes.body}>
- <Content markdown={comment.message} />
+ {comment.message !== '' ? (
+ <Content markdown={comment.message} />
+ ) : (
+ <Content markdown="*No description provided.*" />
+ )}
</section>
</Paper>
);
diff --git a/webui/src/pages/bug/MessageHistoryDialog.tsx b/webui/src/pages/bug/MessageHistoryDialog.tsx
index 5879a373..df8915d9 100644
--- a/webui/src/pages/bug/MessageHistoryDialog.tsx
+++ b/webui/src/pages/bug/MessageHistoryDialog.tsx
@@ -111,6 +111,7 @@ const AccordionSummary = withStyles((theme) => ({
const AccordionDetails = withStyles((theme) => ({
root: {
display: 'block',
+ overflow: 'auto',
padding: theme.spacing(2),
},
}))(MuiAccordionDetails);
@@ -229,7 +230,11 @@ function MessageHistoryDialog({ bugId, commentId, open, onClose }: Props) {
<Typography>{getSummary(index, edit.date)}</Typography>
</AccordionSummary>
<AccordionDetails>
- <Content markdown={edit.message} />
+ {edit.message !== '' ? (
+ <Content markdown={edit.message} />
+ ) : (
+ <Content markdown="*No description provided.*" />
+ )}
</AccordionDetails>
</Accordion>
))}
diff --git a/webui/src/pages/list/BugRow.tsx b/webui/src/pages/list/BugRow.tsx
index 562149f3..68a3b299 100644
--- a/webui/src/pages/list/BugRow.tsx
+++ b/webui/src/pages/list/BugRow.tsx
@@ -84,7 +84,9 @@ const useStyles = makeStyles((theme) => ({
},
commentCount: {
fontSize: '1rem',
+ minWidth: '2rem',
marginLeft: theme.spacing(0.5),
+ marginRight: theme.spacing(1),
},
commentCountCell: {
display: 'inline-flex',
diff --git a/webui/src/pages/list/Filter.tsx b/webui/src/pages/list/Filter.tsx
index 3559b3ce..496fb3ba 100644
--- a/webui/src/pages/list/Filter.tsx
+++ b/webui/src/pages/list/Filter.tsx
@@ -35,52 +35,50 @@ const ITEM_HEIGHT = 48;
export type Query = { [key: string]: string[] };
function parse(query: string): Query {
- // TODO: extract the rest of the query?
const params: Query = {};
-
- // TODO: support escaping without quotes
- const re = /(\w+):([A-Za-z0-9-]+|(["'])(([^\3]|\\.)*)\3)+/g;
+ let re = new RegExp(/([^:\s]+)(:('[^']*'\S*|"[^"]*"\S*|\S*))?/, 'g');
let matches;
while ((matches = re.exec(query)) !== null) {
if (!params[matches[1]]) {
params[matches[1]] = [];
}
-
- let value;
- if (matches[4]) {
- value = matches[4];
+ if (matches[3] !== undefined) {
+ params[matches[1]].push(matches[3]);
} else {
- value = matches[2];
+ params[matches[1]].push('');
}
- value = value.replace(/\\(.)/g, '$1');
- params[matches[1]].push(value);
}
return params;
}
function quote(value: string): string {
- const hasSingle = value.includes("'");
- const hasDouble = value.includes('"');
const hasSpaces = value.includes(' ');
- if (!hasSingle && !hasDouble && !hasSpaces) {
- return value;
- }
+ const isSingleQuotedRegEx = RegExp(/^'.*'$/);
+ const isDoubleQuotedRegEx = RegExp(/^".*"$/);
+ const isQuoted = () =>
+ isDoubleQuotedRegEx.test(value) || isSingleQuotedRegEx.test(value);
- if (!hasDouble) {
- return `"${value}"`;
+ //Test if label name contains whitespace between quotes. If no quoates but
+ //whitespace, then quote string.
+ if (!isQuoted() && hasSpaces) {
+ value = `"${value}"`;
}
- if (!hasSingle) {
- return `'${value}'`;
+ //Convert single quote (tick) to double quote. This way quoting is always
+ //uniform and can be relied upon by the label menu
+ const hasSingle = value.includes(`'`);
+ if (hasSingle) {
+ value = value.replace(/'/g, `"`);
}
- value = value.replace(/"/g, '\\"');
- return `"${value}"`;
+ return value;
}
function stringify(params: Query): string {
const parts: string[][] = Object.entries(params).map(([key, values]) => {
- return values.map((value) => `${key}:${quote(value)}`);
+ return values.map((value) =>
+ value.length > 0 ? `${key}:${quote(value)}` : key
+ );
});
return new Array<string>().concat(...parts).join(' ');
}
diff --git a/webui/src/pages/list/FilterToolbar.tsx b/webui/src/pages/list/FilterToolbar.tsx
index e109578d..4ac579f5 100644
--- a/webui/src/pages/list/FilterToolbar.tsx
+++ b/webui/src/pages/list/FilterToolbar.tsx
@@ -56,6 +56,16 @@ function CountingFilter({ query, children, ...props }: CountingFilterProps) {
);
}
+function quoteLabel(value: string) {
+ const hasUnquotedColon = RegExp(/^[^'"].*:.*[^'"]$/);
+ if (hasUnquotedColon.test(value)) {
+ //quote values which contain a colon but are not quoted.
+ //E.g. abc:abc becomes "abc:abc"
+ return `"${value}"`;
+ }
+ return value;
+}
+
type Props = {
query: string;
queryLocation: (query: string) => LocationDescriptor;
@@ -87,7 +97,7 @@ function FilterToolbar({ query, queryLocation }: Props) {
labelsData.repository.validLabels.nodes
) {
labels = labelsData.repository.validLabels.nodes.map((node) => [
- node.name,
+ quoteLabel(node.name),
node.name,
node.color,
]);
@@ -131,7 +141,6 @@ function FilterToolbar({ query, queryLocation }: Props) {
[key]: [],
});
- // TODO: author/label filters
return (
<Toolbar className={classes.toolbar}>
<CountingFilter