import { buildPipelinesRelation , buildTopicsMap , buildTopicsRelation , PipelinesMap , TopicsMap } from '@/services/data/pipeline/pipeline-relations' ; import { ConnectedSpace } from '@/services/data/tuples/connected-space-types' ; import { Pipeline } from '@/services/data/tuples/pipeline-types' ; import { Space } from '@/services/data/tuples/space-types' ; import { Subject } from '@/services/data/tuples/subject-types' ; import { Topic , TopicId } from '@/services/data/tuples/topic-types' ; import { CheckBox } from '@/widgets/basic/checkbox' ; import { Input } from '@/widgets/basic/input' ; import { useForceUpdate } from '@/widgets/basic/utils' ; import React , { ChangeEvent , Fragment , useState } from 'react' ; import { Candidate , ConnectedSpaceCandidate , PipelineCandidate , SpaceCandidate , SubjectCandidate , TopicCandidate } from './types' ; import { getCandidateKey , getCandidateName , getCandidateType , isConnectedSpaceCandidate , isPipelineCandidate , isSpaceCandidate , isSubjectCandidate , isTopicCandidate } from './utils' ; import { PickerTableBody , PickerTableBodyCell , PickerTableBodyRow , PickerTableHeader , PickerTableHeaderCell , SubItemPrefix } from './widgets' ; interface Filter { value : string ; handler ? : number ; } interface Candidates { topics : Array < TopicCandidate > ; pipelines : Array < PipelineCandidate > ; spaces : Array < SpaceCandidate > ; connectedSpaces : Array < ConnectedSpaceCandidate > ; subjects : Array < SubjectCandidate > ; } type SpacesMap = Record < string , Space > ; type ConnectedSpacesMap = Record < string , ConnectedSpace > ; type SubjectsMap = Record < string , Subject > ; interface TopicRelations { pipelines : Array < PipelineCandidate > ; spaces : Array < SpaceCandidate > ; connectedSpaces : Array < ConnectedSpaceCandidate > ; subjects : Array < SubjectCandidate > ; } interface PipelineRelations { topics : Array < TopicCandidate > ; } interface SpaceRelations { topics : Array < TopicCandidate > ; connectedSpaces : Array < ConnectedSpaceCandidate > ; subjects : Array < SubjectCandidate > ; } interface ConnectedSpaceRelations { topics : Array < TopicCandidate > ; spaces : Array < SpaceCandidate > ; subjects : Array < SubjectCandidate > ; } interface SubjectRelations { topics : Array < TopicCandidate > ; spaces : Array < SpaceCandidate > ; connectedSpaces : Array < ConnectedSpaceCandidate > ; } const isCandidateExisted = ( candidate , existsMap ) : boolean => { if ( isTopicCandidate ( candidate ) ) { if ( existsMap . topics [ candidate . topic . topicId ] != null ) { return true ; } } else if ( isPipelineCandidate ( candidate ) ) { if ( existsMap . pipelines [ candidate . pipeline . pipelineId ] != null ) { return true ; } } else if ( isSpaceCandidate ( candidate ) ) { if ( existsMap . spaces [ candidate . space . spaceId ] != null ) { return true ; } } else if ( isConnectedSpaceCandidate ( candidate ) ) { if ( existsMap . connectedSpaces [ candidate . connectedSpace . spaceId ] != null ) { return true ; } } else if ( isSubjectCandidate ( candidate ) ) { if ( existsMap . subjects [ candidate . subject . subjectId ] != null ) { return true ; } } return false ; } ; const buildCandidatesRelationship = ( candidates ) => { const topics = ( candidates . topics || [ ] ) . map ( ( { topic } ) => topic ) ; const pipelines = ( candidates . pipelines || [ ] ) . map ( ( { pipeline } ) => pipeline ) ; const topicsMap = buildTopicsMap ( topics ) ; const pipelineRelations = buildPipelinesRelation ( pipelines , topicsMap ) ; const topicRelations = buildTopicsRelation ( topics , pipelineRelations ) ; const topicCandidatesMap = candidates . topics . reduce ( ( map , topic ) => { map [ topic . topic . topicId ] = topic ; return map ; } , { } as Record < TopicId , TopicCandidate > ) ; const pipelineCandidatesMap = candidates . pipelines . reduce ( ( map , pipeline ) => { map [ pipeline . pipeline . pipelineId ] = pipeline ; return map ; } , { } as Record < string , PipelineCandidate > ) ; return { topics : ( candidates . topics || [ ] ) . reduce ( ( map , { topic } ) => { const r = map [ topic . topicId ] ?? { pipelines : [ ] , spaces : [ ] , connectedSpaces : [ ] , subjects : [ ] } ; map [ topic . topicId ] = r ; r . pipelines = [ ... new Set ( [ ... ( topicRelations [ topic . topicId ] . readMe || [ ] ) , ... ( topicRelations [ topic . topicId ] . writeMe || [ ] ) , ... ( candidates . pipelines || [ ] ) . filter ( ( { pipeline } ) => pipeline . topicId == topic . topicId ) . map ( ( { pipeline } ) => pipeline ) ] ) ] . map ( pipeline => pipelineCandidatesMap [ pipeline . pipelineId ] ) . filter ( x => ! ! x ) ; r . spaces = ( candidates . spaces || [ ] ) . filter ( ( { space } ) => ( space . topicIds || [ ] ) . some ( topicId => topicId == topic . topicId ) ) ; r . connectedSpaces = ( candidates . connectedSpaces || [ ] ) . filter ( ( { connectedSpace } ) => r . spaces . some ( ( { space } ) => connectedSpace . spaceId == space . spaceId ) ) ; r . subjects = ( candidates . subjects || [ ] ) . filter ( ( { subject } ) => { return r . connectedSpaces . some ( ( { connectedSpace } ) => ( connectedSpace . subjects || [ ] ) . includes ( subject ) ) ; } ) ; return map ; } , { } as Record < TopicId , TopicRelations > ) , pipelines : ( candidates . pipelines || [ ] ) . reduce ( ( map , { pipeline } ) => { const r = map [ pipeline . pipelineId ] ?? { topics : [ ] } ; map [ pipeline . pipelineId ] = r ; r . topics = [ ... new Set ( [ pipelineRelations [ pipeline . pipelineId ] . trigger , ... ( pipelineRelations [ pipeline . pipelineId ] . incoming || [ ] ) , ... ( pipelineRelations [ pipeline . pipelineId ] . outgoing || [ ] ) ] ) ] . filter ( x => ! ! x ) . map ( topic => topic ? topicCandidatesMap [ topic . topic . topicId ] : null ) . filter ( x => ! ! x ) as Array < TopicCandidate > ; return map ; } , { } as Record < string , PipelineRelations > ) , spaces : ( candidates . spaces || [ ] ) . reduce ( ( map , { space } ) => { const r = map [ space . spaceId ] ?? { topics : [ ] , connectedSpaces : [ ] , subjects : [ ] } ; map [ space . spaceId ] = r ; r . topics = ( candidates . topics || [ ] ) . filter ( ( { topic } ) => ( space . topicIds || [ ] ) . some ( topicId => topicId == topic . topicId ) ) ; r . connectedSpaces = ( candidates . connectedSpaces || [ ] ) . filter ( ( { connectedSpace } ) => connectedSpace . spaceId == space . spaceId ) ; r . subjects = ( candidates . subjects || [ ] ) . filter ( ( { subject } ) => { return r . connectedSpaces . some ( ( { connectedSpace } ) => ( connectedSpace . subjects || [ ] ) . includes ( subject ) ) ; } ) ; return map ; } , { } as Record < string , SpaceRelations > ) , connectedSpaces : ( candidates . connectedSpaces || [ ] ) . reduce ( ( map , { connectedSpace } ) => { const r = map [ connectedSpace . connectId ] ?? { subjects : [ ] } ; map [ connectedSpace . connectId ] = r ; r . spaces = ( candidates . spaces || [ ] ) . filter ( ( { space } ) => space . spaceId == connectedSpace . spaceId ) ; const allTopicIds = [ ... new Set ( r . spaces . map ( ( { space } ) => space . topicIds || [ ] ) . flat ( ) ) ] ; r . topics = ( candidates . topics || [ ] ) . filter ( ( { topic } ) => allTopicIds . some ( topicId => topicId == topic . topicId ) ) ; r . subjects = ( candidates . subjects || [ ] ) . filter ( ( { subject } ) => ( connectedSpace . subjects || [ ] ) . includes ( subject ) ) ; return map ; } , { } as Record < string , ConnectedSpaceRelations > ) , subjects : ( candidates . subjects || [ ] ) . reduce ( ( map , { subject } ) => { const r = map [ subject . subjectId ] ?? { connectedSpaces : [ ] } ; map [ subject . subjectId ] = r ; r . connectedSpaces = ( candidates . connectedSpaces || [ ] ) . filter ( ( { connectedSpace } ) => ( connectedSpace . subjects || [ ] ) . includes ( subject ) ) ; r . spaces = ( candidates . spaces || [ ] ) . filter ( ( { space } ) => r . connectedSpaces . some ( ( { connectedSpace } ) => connectedSpace . spaceId == space . spaceId ) ) ; const allTopicIds = [ ... new Set ( r . spaces . map ( ( { space } ) => space . topicIds || [ ] ) . flat ( ) ) ] ; r . topics = ( candidates . topics || [ ] ) . filter ( ( { topic } ) => allTopicIds . some ( topicId => topicId == topic . topicId ) ) ; return map ; } , { } as Record < string , SubjectRelations > ) } ; } ; const CandidatePrefix = ( props ) => { const { me } = props ; if ( isConnectedSpaceCandidate ( me ) ) { return < SubItemPrefix />; } else if ( isSubjectCandidate ( me ) ) { return < > < SubItemPrefix /> < SubItemPrefix / > < />; } return < Fragment />; } ; export const ImportPickerTable = ( props ) => { const { candidates , cachedTopics , cachedPipelines , cachedSpaces , cachedConnectedSpaces } = props ; const { topics : topicCandidates , pipelines : pipelineCandidates , spaces : spaceCandidates , connectedSpaces : connectedSpaceCandidates , subjects : subjectCandidates } = candidates ; const [ items ] = useState ( ( ) => { return [ ... topicCandidates , ... pipelineCandidates , ... spaceCandidates . map ( spaceCandidate => { const spaceId = spaceCandidate . space . spaceId ; return [ spaceCandidate , ... connectedSpaceCandidates . filter ( connectedSpaceCandidate => connectedSpaceCandidate . connectedSpace . spaceId == spaceId ) . map ( connectedSpaceCandidate => { const subjectIds = ( connectedSpaceCandidate . connectedSpace . subjects || [ ] ) . map ( subject => subject . subjectId ) ; return [ connectedSpaceCandidate , ... subjectCandidates . filter ( subjectCandidate => subjectIds . some ( subjectId => subjectId == subjectCandidate . subject . subjectId ) ) ] ; } ) . flat ( ) ] ; } ) . flat ( ) ] ; } ) ; const [ relations ] = useState ( buildCandidatesRelationship ( candidates ) ) ; const [ displayItems , setDisplayItems ] = useState ( items ) ; const [ filter , setFilter ] = useState < Filter > ( { value : '' } ) ; const forceUpdate = useForceUpdate ( ) ; const onFilterTextChanged = ( event ) => { const { value } = event . target ; if ( filter . handler ) { window . clearTimeout ( filter . handler ) ; } setFilter ( { value , handler : window . setTimeout ( ( ) => { delete filter . handler ; const text = value . trim ( ) . toLowerCase ( ) ; if ( text === '' ) { setDisplayItems ( items ) ; } else { setDisplayItems ( items . filter ( candidate => { return getCandidateName ( candidate ) . toLowerCase ( ) . includes ( text ) ; } ) ) ; } } , 300 ) } ) ; } ; const onAllSelectionChange = ( ) => { const allSelected = displayItems . every ( item => item . picked ) ; if ( allSelected ) { displayItems . forEach ( item => item . picked = false ) ; } else { displayItems . forEach ( item => item . picked = true ) ; } forceUpdate ( ) ; } ; const onSelectionChange = ( candidate ) => ( value ) => { candidate . picked = value ; if ( isTopicCandidate ( candidate ) && ! candidate . picked ) { const { pipelines , spaces , connectedSpaces , subjects } = relations . topics [ candidate . topic . topicId ] || { } ; [ ... pipelines , ... spaces , ... connectedSpaces , ... subjects ] . forEach ( candidate => candidate . picked = false ) ; } else if ( isPipelineCandidate ( candidate ) && candidate . picked ) { const { topics } = relations . pipelines [ candidate . pipeline . pipelineId ] || { } ; topics . forEach ( candidate => candidate . picked = true ) ; } else if ( isSpaceCandidate ( candidate ) ) { if ( candidate . picked ) { const { topics } = relations . spaces [ candidate . space . spaceId ] || { } ; topics . forEach ( candidate => candidate . picked = true ) ; } else { const { connectedSpaces , subjects } = relations . spaces [ candidate . space . spaceId ] || { } ; [ ... connectedSpaces , ... subjects ] . filter ( x => ! ! x ) . forEach ( candidate => candidate . picked = false ) ; } } else if ( isConnectedSpaceCandidate ( candidate ) ) { const { topics , spaces , subjects } = relations . connectedSpaces [ candidate . connectedSpace . connectId ] || { } ; if ( candidate . picked ) { [ ... topics , ... spaces , ... subjects ] . forEach ( candidate => candidate . picked = true ) ; } else { subjects . forEach ( candidate => candidate . picked = false ) ; } } else if ( isSubjectCandidate ( candidate ) && candidate . picked ) { const { topics , spaces , connectedSpaces } = relations . subjects [ candidate . subject . subjectId ] || { } ; [ ... topics , ... spaces , ... connectedSpaces ] . forEach ( candidate => candidate . picked = true ) ; } forceUpdate ( ) ; } ; const allSelected = displayItems . every ( item => item . picked ) ; const topicsMap = cachedTopics . reduce ( ( map , topic ) => { map [ topic . topicId ] = topic ; return map ; } , { } as TopicsMap ) ; const pipelinesMap = cachedPipelines . reduce ( ( map , pipeline ) => { map [ pipeline . pipelineId ] = pipeline ; return map ; } , { } as PipelinesMap ) ; const spacesMap = cachedSpaces . reduce ( ( map , space ) => { map [ space . spaceId ] = space ; return map ; } , { } as SpacesMap ) ; const { connectedSpacesMap , subjectsMap } = cachedConnectedSpaces . reduce ( ( map , connectedSpace ) => { map . connectedSpacesMap [ connectedSpace . connectId ] = connectedSpace ; ( connectedSpace . subjects || [ ] ) . forEach ( subject => map . subjectsMap [ subject . subjectId ] = subject ) ; return map ; } , { connectedSpacesMap : { } as ConnectedSpacesMap , subjectsMap : { } as SubjectsMap } ) ; return < > < PickerTableHeader > < PickerTableHeaderCell > # < /PickerTableHeaderCell> < PickerTableHeaderCell > < CheckBox value = { allSelected } data - checked = { allSelected } onChange = { onAllSelectionChange } / > < /PickerTableHeaderCell> < PickerTableHeaderCell > Tuple Type < /PickerTableHeaderCell> < PickerTableHeaderCell > < span > Name < /span> < Input placeholder = "Filter by name..." value = { filter . value } onChange = { onFilterTextChanged } / > < /PickerTableHeaderCell> < /PickerTableHeader> < PickerTableBody > { displayItems . map ( ( candidate , index ) => { return < PickerTableBodyRow columns = { 5 } key = { getCandidateKey ( candidate ) } > < PickerTableBodyCell > { index + 1 } < /PickerTableBodyCell> < PickerTableBodyCell > < CheckBox value = { candidate . picked } data - checked = { candidate . picked } onChange = { onSelectionChange ( candidate ) } / > < /PickerTableBodyCell> < PickerTableBodyCell > { getCandidateType ( candidate ) } < /PickerTableBodyCell> < PickerTableBodyCell > < CandidatePrefix me = { candidate } items = { displayItems } / > < span > { getCandidateName ( candidate ) } < /span> < /PickerTableBodyCell> < PickerTableBodyCell > { isCandidateExisted ( candidate , { topics : topicsMap , pipelines : pipelinesMap , spaces : spacesMap , connectedSpaces : connectedSpacesMap , subjects : subjectsMap } ) ? 'Already exists.' : '' } < /PickerTableBodyCell> < /PickerTableBodyRow>; } ) . filter ( x => x ) } < /PickerTableBody> < />; } ;