@@ -2,14 +2,12 @@ import Head from 'next/head'
22import { useState , useEffect } from 'react'
33import styles from '../../../styles/Home.module.css'
44import {
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'
77import dotenv from 'dotenv'
88import path from 'path'
99import * as fs from 'fs/promises'
1010import { existsSync } from 'fs'
11- import { findStrBetween } from '../../../utils/findStrBetween'
12- import { readAndSet } from '../../../utils/readAndSet'
1311import Nav from '../../../components/Nav'
1412import Sidebar from '../../../components/Sidebar'
1513import Content from '../../../components/Content'
@@ -18,6 +16,25 @@ import MappingTag from './MappingTag'
1816import MappingTagStatus from './MappingTagStatus'
1917import 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+
2138export 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 = / ^ J I R A _ I S S U E _ ( [ A - Z ] + ) _ S T A T U S _ M A P P I N G $ / . 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