Skip to content

Commit cfde573

Browse files
authored
Merge pull request #475 from merico-dev/fix-config-ui-again
Fix config UI again
2 parents 418543f + cd213e9 commit cfde573

File tree

4 files changed

+94
-166
lines changed

4 files changed

+94
-166
lines changed

config-ui/next.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module.exports = {
2-
pageExtensions: ['page.jsx'],
2+
pageExtensions: ['page.jsx', 'js'],
33

44
webpack: (config, { isServer }) => {
55
if (!isServer) {

config-ui/pages/plugins/jira/MappingTag.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const MappingTag = ({labelIntent, labelName, onChange, rightElement, helperText,
1919
placeholder="Add Tags..."
2020
values={values || []}
2121
fill={true}
22-
onChange={onChange}
22+
onChange={value => onChange([...new Set(value)])}
2323
addOnPaste={true}
2424
rightElement={rightElement}
2525
onKeyDown={e => e.key === 'Enter' && e.preventDefault()}

config-ui/pages/plugins/jira/MappingTagStatus.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import MappingTag from './MappingTag'
33
const MappingTagStatus = ({reqValue, resValue, envName, clearBtnReq, clearBtnRes, onChangeReq, onChangeRes}) => {
44
return <>
55
<MappingTag
6-
labelName="Requirement"
6+
labelName="Rejected"
77
labelIntent="danger"
88
values={reqValue}
99
helperText={envName}

config-ui/pages/plugins/jira/index.page.jsx

Lines changed: 91 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ import Head from 'next/head'
22
import { useState, useEffect } from 'react'
33
import styles from '../../../styles/Home.module.css'
44
import {
5-
Tooltip, Position, FormGroup, InputGroup, Button, Label, Icon, Classes, Tab, Tabs, Overlay, Dialog
5+
Tooltip, Position, FormGroup, InputGroup, Button, Label, Icon, Classes, Dialog
66
} from '@blueprintjs/core'
77
import dotenv from 'dotenv'
88
import path from 'path'
99
import * as fs from 'fs/promises'
1010
import { existsSync } from 'fs'
11-
import { findStrBetween } from '../../../utils/findStrBetween'
12-
import { readAndSet } from '../../../utils/readAndSet'
1311
import Nav from '../../../components/Nav'
1412
import Sidebar from '../../../components/Sidebar'
1513
import Content from '../../../components/Content'
@@ -18,6 +16,25 @@ import MappingTag from './MappingTag'
1816
import MappingTagStatus from './MappingTagStatus'
1917
import ClearButton from './ClearButton'
2018

19+
function parseMapping(mappingString) {
20+
const mapping = {}
21+
if (!mappingString.trim()) {
22+
return mapping
23+
}
24+
for (const item of mappingString.split(";")) {
25+
let [standard, customs] = item.split(":")
26+
standard = standard.trim()
27+
mapping[standard] = mapping[standard] || []
28+
if (!customs) {
29+
continue
30+
}
31+
for (const custom of customs.split(",")) {
32+
mapping[standard].push(custom.trim())
33+
}
34+
}
35+
return mapping
36+
}
37+
2138
export default function Home(props) {
2239
const { env } = props
2340

@@ -30,22 +47,56 @@ export default function Home(props) {
3047
const [jiraBoardGitlabeProjects, setJiraBoardGitlabeProjects] = useState(env.JIRA_BOARD_GITLAB_PROJECTS)
3148

3249
// Type mappings state
33-
const [typeMappingBug, setTypeMappingBug] = useState([])
34-
const [typeMappingIncident, setTypeMappingIncident] = useState([])
35-
const [typeMappingRequirement, setTypeMappingRequirement] = useState([])
50+
const defaultTypeMapping = parseMapping(env.JIRA_ISSUE_TYPE_MAPPING)
51+
const [typeMappingBug, setTypeMappingBug] = useState(defaultTypeMapping.Bug || [])
52+
const [typeMappingIncident, setTypeMappingIncident] = useState(defaultTypeMapping.Incident || [])
53+
const [typeMappingRequirement, setTypeMappingRequirement] = useState(defaultTypeMapping.Requirement || [])
3654
const [typeMappingAll, setTypeMappingAll] = useState()
3755

38-
// Status mappings state
56+
// status mapping
57+
const defaultStatusMappings = []
58+
for (const [key, value] of Object.entries(env)) {
59+
const m = /^JIRA_ISSUE_([A-Z]+)_STATUS_MAPPING$/.exec(key)
60+
if (!m) {
61+
continue
62+
}
63+
const type = m[1]
64+
defaultStatusMappings.push({
65+
type,
66+
key,
67+
mapping: parseMapping(value)
68+
})
69+
}
70+
const [statusMappings, setStatusMappings] = useState(defaultStatusMappings)
71+
function setStatusMapping(key, values, status) {
72+
setStatusMappings(statusMappings.map(mapping => {
73+
if (mapping.key === key) {
74+
mapping.mapping[status] = values
75+
}
76+
return mapping
77+
}))
78+
}
3979
const [customStatusOverlay, setCustomStatusOverlay] = useState(false)
40-
const [statusTabId, setStatusTabId] = useState(0)
41-
const [statusMappingReqBug, setStatusMappingReqBug] = useState([])
42-
const [statusMappingResBug, setStatusMappingResBug] = useState([])
43-
const [statusMappingReqIncident, setStatusMappingReqIncident] = useState([])
44-
const [statusMappingResIncident, setStatusMappingResIncident] = useState([])
45-
const [statusMappingReqStory, setStatusMappingReqStory] = useState([])
46-
const [statusMappingResStory, setStatusMappingResStory] = useState([])
47-
const [customStatus, setCustomStatus] = useState([])
4880
const [customStatusName, setCustomStatusName] = useState('')
81+
function addStatusMapping(e) {
82+
const type = customStatusName.trim().toUpperCase()
83+
if (statusMappings.find(e => e.type === type)) {
84+
return
85+
}
86+
setStatusMappings([
87+
...statusMappings,
88+
{
89+
type,
90+
key: `JIRA_ISSUE_${type}_STATUS_MAPPING`,
91+
mapping: {
92+
Resolved: [],
93+
Rejected: [],
94+
}
95+
}
96+
])
97+
setCustomStatusOverlay(false)
98+
e.preventDefault()
99+
}
49100

50101

51102
function updateEnv(key, value) {
@@ -58,19 +109,14 @@ export default function Home(props) {
58109
updateEnv('JIRA_BASIC_AUTH_ENCODED', jiraBasicAuthEncoded)
59110
updateEnv('JIRA_ISSUE_EPIC_KEY_FIELD', jiraIssueEpicKeyField)
60111
updateEnv('JIRA_ISSUE_TYPE_MAPPING', typeMappingAll)
61-
updateEnv('JIRA_ISSUE_BUG_STATUS_MAPPING', `Requirement:${statusMappingReqBug};Resolved:${statusMappingResBug};`)
62-
updateEnv('JIRA_ISSUE_INCIDENT_STATUS_MAPPING', `Requirement:${statusMappingReqIncident};Resolved:${statusMappingResIncident};`)
63-
updateEnv('JIRA_ISSUE_STORY_STATUS_MAPPING', `Requirement:${statusMappingReqStory};Resolved:${statusMappingResStory};`)
64112
updateEnv('JIRA_ISSUE_STORYPOINT_COEFFICIENT', jiraIssueStoryCoefficient)
65113
updateEnv('JIRA_ISSUE_STORYPOINT_FIELD', jiraIssueStoryPointField)
66114
updateEnv('JIRA_BOARD_GITLAB_PROJECTS', jiraBoardGitlabeProjects)
67115

68116
// Save all custom status data
69-
customStatus.map(status => {
70-
const requirement = status.reqValue.toString()
71-
const resolved = status.resValue.toString()
72-
const name = `JIRA_ISSUE_${status.name.toUpperCase()}_STATUS_MAPPING`
73-
updateEnv(name, `Requirement:${requirement};Resolved:${resolved};`)
117+
statusMappings.map(mapping => {
118+
const { Resolved, Rejected } = mapping.mapping
119+
updateEnv(mapping.key, `Rejected:${Rejected ? Rejected.join(',') : ''};Resolved:${Resolved ? Resolved.join(',') : ''};`)
74120
})
75121

76122
setAlertOpen(true)
@@ -84,68 +130,6 @@ export default function Home(props) {
84130
setTypeMappingAll(all)
85131
}, [typeMappingBug, typeMappingIncident, typeMappingRequirement])
86132

87-
useEffect(() => {
88-
// Load type & status mappings
89-
const envStr = [
90-
env.JIRA_ISSUE_TYPE_MAPPING,
91-
env.JIRA_ISSUE_BUG_STATUS_MAPPING,
92-
env.JIRA_ISSUE_INCIDENT_STATUS_MAPPING,
93-
env.JIRA_ISSUE_STORY_STATUS_MAPPING
94-
]
95-
const fields = [
96-
{
97-
tagName: 'Bug:', tagLen: 4, isStatus: false, str: envStr[0],
98-
fn1: (arr) => setTypeMappingBug(arr)
99-
},
100-
{
101-
tagName: 'Incident:', tagLen: 9, isStatus: false, str: envStr[0],
102-
fn1: (arr) => setTypeMappingIncident(arr)
103-
},
104-
{
105-
tagName: 'Requirement:', tagLen: 12, isStatus: false, str: envStr[0],
106-
fn1: (arr) => setTypeMappingRequirement(arr)
107-
},
108-
{
109-
tagName: 'Bug:', tagLen: null, isStatus: true, str: envStr[1],
110-
fn1: (arr) => setStatusMappingReqBug(arr),
111-
fn2: (arr) => setStatusMappingResBug(arr)
112-
},
113-
{
114-
tagName: 'Incident:', tagLen: null, isStatus: true, str: envStr[2],
115-
fn1: (arr) => setStatusMappingReqIncident(arr),
116-
fn2: (arr) => setStatusMappingResIncident(arr)
117-
},
118-
{
119-
tagName: 'Story:', tagLen: null, isStatus: true, str: envStr[3],
120-
fn1: (arr) => setStatusMappingReqStory(arr),
121-
fn2: (arr) => setStatusMappingResStory(arr)
122-
},
123-
]
124-
125-
fields.map(field => {
126-
readAndSet(field.tagName, field.tagLen, field.isStatus, field.str, field.fn1, field.fn2)
127-
})
128-
129-
//Load custom status mappings
130-
for (const field in env) {
131-
const bug = 'JIRA_ISSUE_BUG'
132-
const incident = 'JIRA_ISSUE_INCIDENT'
133-
const story = 'JIRA_ISSUE_STORY'
134-
const isStatusMapping = field.includes('_STATUS_MAPPING')
135-
const isNotDefault = (!field.includes(bug)) && (!field.includes(incident)) && (!field.includes(story))
136-
137-
if (isStatusMapping && isNotDefault) {
138-
const strName = field.slice(11, -15)
139-
const strValuesReq = findStrBetween(env[field], 'Requirement:', ';')
140-
const strValuesRes = findStrBetween(env[field], 'Resolved:', ';')
141-
const req = strValuesReq[0].slice(12, -1).split(',')
142-
const res = strValuesRes[0].slice(9, -1).split(',')
143-
144-
setCustomStatus(customStatus => [...customStatus, {name: strName, reqValue: req || '', resValue: res || ''}])
145-
}
146-
}
147-
}, [])
148-
149133
return (
150134
<div className={styles.container}>
151135

@@ -254,90 +238,39 @@ export default function Home(props) {
254238
<p className={styles.description}>Map your own issue statuses to Dev Lake's standard statuses for every issue type</p>
255239
</div>
256240

257-
<div className={styles.formContainer}>
258-
259-
<Tabs id="StatusMappings" onChange={(id) => setStatusTabId(id)} selectedTabId={statusTabId} className={styles.statusTabs}>
260-
<Tab id={0} title="Bug" panel={
261-
<MappingTagStatus
262-
reqValue={statusMappingReqBug}
263-
resValue={statusMappingResBug}
264-
envName="JIRA_ISSUE_BUG_STATUS_MAPPING"
265-
clearBtnReq={<ClearButton onClick={() => setStatusMappingReqBug([])} />}
266-
clearBtnRes={<ClearButton onClick={() => setStatusMappingResBug([])} />}
267-
onChangeReq={(values) => setStatusMappingReqBug(values)}
268-
onChangeRes={(values) => setStatusMappingResBug(values)}
269-
/>
270-
} />
271-
<Tab id={1} title="Incident" panel={
272-
<MappingTagStatus
273-
reqValue={statusMappingReqIncident}
274-
resValue={statusMappingResIncident}
275-
envName="JIRA_ISSUE_INCIDENT_STATUS_MAPPING"
276-
clearBtnReq={<ClearButton onClick={() => setStatusMappingReqIncident([])} />}
277-
clearBtnRes={<ClearButton onClick={() => setStatusMappingResIncident([])} />}
278-
onChangeReq={(values) => setStatusMappingReqIncident(values)}
279-
onChangeRes={(values) => setStatusMappingResIncident(values)}
280-
/>
281-
} />
282-
<Tab id={2} title="Story" panel={
283-
<MappingTagStatus
284-
reqValue={statusMappingReqStory}
285-
resValue={statusMappingResStory}
286-
envName="JIRA_ISSUE_STORY_STATUS_MAPPING"
287-
clearBtnReq={<ClearButton onClick={() => setStatusMappingReqStory([])} />}
288-
clearBtnRes={<ClearButton onClick={() => setStatusMappingResStory([])} />}
289-
onChangeReq={(values) => setStatusMappingResStory(values)}
290-
onChangeRes={(values) => setStatusMappingResStory(values)}
291-
/>
292-
} />
293-
294-
{customStatus.length > 0 && customStatus.map((status, i) => {
295-
const statusAll = customStatus.filter(obj => obj.name != status.name)
296-
const mapObj = customStatus.find(obj => obj.name === status.name)
297-
298-
return <Tab id={i + 3} key={i} title={status.name} panel={
299-
<MappingTagStatus
300-
reqValue={mapObj.reqValue.length > 0 ? mapObj.reqValue : []}
301-
resValue={mapObj.resValue.length > 0 ? mapObj.resValue : []}
302-
envName={`JIRA_ISSUE_${status.name.toUpperCase()}_STATUS_MAPPING`}
303-
clearBtnReq={<ClearButton onClick={() => {
304-
mapObj.reqValue = []
305-
setCustomStatus([...statusAll, mapObj])
306-
}} />}
307-
clearBtnRes={<ClearButton onClick={() => {
308-
mapObj.resValue = []
309-
setCustomStatus([...statusAll, mapObj])
310-
}} />}
311-
onChangeReq={(values) => {
312-
mapObj.reqValue = values
313-
setCustomStatus([...statusAll, mapObj])
314-
}}
315-
onChangeRes={(values) => {
316-
mapObj.resValue = values
317-
setCustomStatus([...statusAll, mapObj])
318-
}}
319-
/>
320-
} />
321-
})}
322-
241+
<div className={styles.formContainer} style={{height: 'auto', flexWrap: 'wrap'}}>
242+
{statusMappings.length > 0 && statusMappings.map((statusMapping, i) =>
243+
<div
244+
key={statusMapping.key} style={{width: "100%"}}>
245+
<p>Mapping {statusMapping.type} </p>
246+
<div style={{marginLeft: "2em"}}>
247+
<MappingTagStatus
248+
reqValue={statusMapping.mapping.Rejected || []}
249+
resValue={statusMapping.mapping.Resolved || []}
250+
envName={statusMapping.key}
251+
clearBtnReq={<ClearButton onClick={() => setStatusMapping(statusMapping.key, [], 'Rejected')} />}
252+
clearBtnRes={<ClearButton onClick={() => setStatusMapping(statusMapping.key, [], 'Resolved')} />}
253+
onChangeReq={values => setStatusMapping(statusMapping.key, values, 'Rejected')}
254+
onChangeRes={values => setStatusMapping(statusMapping.key, values, 'Resolved')}
255+
style={{paddingLeft: '2em', boxSizing: 'border-box'}}
256+
/>
257+
</div>
258+
</div>
259+
)}
323260
<Button icon="add" onClick={() => setCustomStatusOverlay(true)} className={styles.addNewStatusBtn}>Add New</Button>
324261

325262
<Dialog
326263
style={{ width: '100%', maxWidth: "664px", height: "auto" }}
327264
icon="diagram-tree"
328265
onClose={() => setCustomStatusOverlay(false)}
329-
title="Add a New Custom Status"
266+
title="Add a New Status Mapping"
330267
isOpen={customStatusOverlay}
331268
onOpened={() => setCustomStatusName('')}
332269
autoFocus={false}
333270
className={styles.customStatusDialog}
334271
>
335272
<div className={Classes.DIALOG_BODY}>
336-
<form onSubmit={(e) => {
337-
e.preventDefault()
338-
setCustomStatus([...customStatus, {name: customStatusName, reqValue: '', resValue: ''}])
339-
setCustomStatusOverlay(false)
340-
}}>
273+
<form onSubmit={addStatusMapping}>
341274
<FormGroup
342275
className={styles.formGroup}
343276
className={styles.customStatusFormGroup}
@@ -349,19 +282,14 @@ export default function Home(props) {
349282
className={styles.customStatusInput}
350283
autoFocus={true}
351284
/>
352-
<Button icon="add" onClick={() => {
353-
setCustomStatus([...customStatus, {name: customStatusName, reqValue: '', resValue: ''}])
354-
setCustomStatusOverlay(false)
355-
}}
285+
<Button icon="add" onClick={addStatusMapping}
356286
className={styles.addNewStatusBtnDialog}
357287
onSubmit={(e) => e.preventDefault()}>Add New</Button>
358288
</FormGroup>
359289
</form>
360290
</div>
361291
</Dialog>
362292

363-
<Tabs.Expander />
364-
</Tabs>
365293
</div>
366294

367295
<div className={styles.headlineContainer}>

0 commit comments

Comments
 (0)