forked from kids-first/kf-portal-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
kids-first#3346 duplicate freactal fences provider into redux
- Loading branch information
1 parent
a4550db
commit 00517ea
Showing
14 changed files
with
570 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { useEffect } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
|
||
import { | ||
concatAllFencesAcls, | ||
fetchAllFencesConnectionsIfNeeded, | ||
} from 'store/actionCreators/fenceConnections'; | ||
import { Api } from 'store/apiTypes'; | ||
import { DispatchFenceConnections, FenceConnections } from 'store/fenceConnectionsTypes'; | ||
import { FencesAllConcatenatedAcls } from 'store/fenceTypes'; | ||
import { RootState } from 'store/rootState'; | ||
import { | ||
selectFenceConnections, | ||
selectIsFetchingAllFenceConnections, | ||
} from 'store/selectors/fenceConnections'; | ||
|
||
type Output = { | ||
isFetchingAllFenceConnections: boolean; | ||
fenceConnections: FenceConnections; | ||
fencesAllAcls: FencesAllConcatenatedAcls; | ||
}; | ||
|
||
const useFenceConnections = (api: Api, fences: string[]): Output => { | ||
const fenceConnections = useSelector((state: RootState) => selectFenceConnections(state)); | ||
const isFetchingAllFenceConnections = useSelector((state: RootState) => | ||
selectIsFetchingAllFenceConnections(state), | ||
); | ||
const dispatch: DispatchFenceConnections = useDispatch(); | ||
|
||
useEffect(() => { | ||
dispatch(fetchAllFencesConnectionsIfNeeded(api, fences)); | ||
}, [fences, dispatch, api]); | ||
|
||
return { | ||
isFetchingAllFenceConnections, | ||
fenceConnections, | ||
fencesAllAcls: concatAllFencesAcls(fenceConnections), | ||
}; | ||
}; | ||
export default useFenceConnections; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useEffect } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
|
||
import { computeAclsByFence } from 'store/actionCreators/fenceConnections'; | ||
import { | ||
computeAllFencesAuthStudies, | ||
fetchAllFenceStudiesIfNeeded, | ||
} from 'store/actionCreators/fenceStudies'; | ||
import { Api } from 'store/apiTypes'; | ||
import { DispatchFenceStudies, FenceStudies, FenceStudy } from 'store/fenceStudiesTypes'; | ||
import { RootState } from 'store/rootState'; | ||
import { selectFenceConnections } from 'store/selectors/fenceConnections'; | ||
import { selectFenceStudies, selectIsFetchingAllFenceStudies } from 'store/selectors/fenceStudies'; | ||
|
||
const { keys } = Object; | ||
|
||
type Output = { | ||
isFetchingAllFenceStudies: boolean; | ||
fenceStudies: FenceStudies; | ||
fenceAuthStudies: FenceStudy[]; | ||
}; | ||
|
||
const useFenceStudies = (api: Api): Output => { | ||
const dispatch: DispatchFenceStudies = useDispatch(); | ||
|
||
const isFetchingAllFenceStudies = useSelector((state: RootState) => | ||
selectIsFetchingAllFenceStudies(state), | ||
); | ||
const fenceStudies = useSelector((state: RootState) => selectFenceStudies(state)); | ||
const fenceConnections = useSelector((state: RootState) => selectFenceConnections(state)); | ||
|
||
useEffect(() => { | ||
const fences = keys(fenceConnections); | ||
const shouldFetch = fences.length > 0; | ||
if (shouldFetch) { | ||
const aclsByFence = computeAclsByFence(fenceConnections); | ||
dispatch(fetchAllFenceStudiesIfNeeded(api, fences, aclsByFence)); | ||
} | ||
}, [fenceConnections, dispatch, api]); | ||
|
||
return { | ||
isFetchingAllFenceStudies, | ||
fenceStudies, | ||
fenceAuthStudies: computeAllFencesAuthStudies(fenceStudies), | ||
}; | ||
}; | ||
export default useFenceStudies; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import keys from 'lodash/keys'; | ||
|
||
import { graphql } from 'services/arranger'; | ||
|
||
export const getAuthStudiesIdAndCount = async (api, fence, userAcl) => | ||
graphql(api)({ | ||
query: ` | ||
query AuthorizedStudyIdsAndCount($sqon: JSON) { | ||
file { | ||
aggregations(filters: $sqon, aggregations_filter_themselves: true){ | ||
participants__study__external_id{ | ||
buckets{ | ||
key | ||
doc_count} | ||
} | ||
} | ||
} | ||
} | ||
`, | ||
variables: { | ||
sqon: { | ||
op: 'and', | ||
content: [ | ||
{ op: 'in', content: { field: 'acl', value: userAcl } }, | ||
{ op: 'in', content: { field: 'repository', value: fence } }, | ||
], | ||
}, | ||
}, | ||
}).then( | ||
({ | ||
data: { | ||
file: { | ||
aggregations: { | ||
participants__study__external_id: { buckets }, | ||
}, | ||
}, | ||
}, | ||
}) => | ||
buckets.reduce((obj, { key, doc_count }) => { | ||
obj[key] = { authorizedFiles: doc_count }; | ||
return obj; | ||
}, {}), | ||
); | ||
|
||
export const getStudiesCountByNameAndAcl = async (api, studies, userAcl) => { | ||
const studyIds = keys(studies); | ||
|
||
const sqons = studyIds.reduce((obj, studyId) => { | ||
obj[`${studyId}_sqon`] = { | ||
op: 'in', | ||
content: { field: 'participants.study.external_id', value: [studyId] }, | ||
}; | ||
return obj; | ||
}, {}); | ||
|
||
return graphql(api)({ | ||
query: ` | ||
query StudyCountByNamesAndAcl(${studyIds.map( | ||
(studyId) => `$${studyId}_sqon: JSON`, | ||
)}) { | ||
file { | ||
${studyIds | ||
.map( | ||
(studyId) => ` | ||
${studyId}: aggregations(filters: $${studyId}_sqon, aggregations_filter_themselves: true) { | ||
acl { | ||
buckets { | ||
key | ||
} | ||
} | ||
participants__study__short_name{ | ||
buckets{ | ||
key | ||
doc_count | ||
} | ||
} | ||
} | ||
`, | ||
) | ||
.join('')} | ||
} | ||
} | ||
`, | ||
variables: sqons, | ||
}).then(({ data: { file } }) => | ||
studyIds.map((id) => { | ||
let study = {}; | ||
const agg = file[id]; | ||
study['acl'] = agg['acl']['buckets'].map((b) => b.key).filter((a) => userAcl.includes(a)); | ||
study['studyShortName'] = agg['participants__study__short_name']['buckets'][0]['key']; | ||
study['totalFiles'] = agg['participants__study__short_name']['buckets'][0]['doc_count']; | ||
study['id'] = id; | ||
study['authorizedFiles'] = studies[id]['authorizedFiles']; | ||
|
||
return study; | ||
}), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import isEmpty from 'lodash/isEmpty'; | ||
import { ThunkAction } from 'redux-thunk'; | ||
|
||
import { getFenceUser } from 'services/fence'; | ||
|
||
import { Api } from '../apiTypes'; | ||
import { | ||
Connection, | ||
FenceConnections, | ||
FenceConnectionsActions, | ||
FenceConnectionsActionTypes, | ||
} from '../fenceConnectionsTypes'; | ||
import { FenceName } from '../fenceTypes'; | ||
import { RootState } from '../rootState'; | ||
import { selectFenceConnections } from '../selectors/fenceConnections'; | ||
|
||
const { entries, keys } = Object; | ||
|
||
const addFenceConnection = ( | ||
fenceName: FenceName, | ||
connection: Connection, | ||
): FenceConnectionsActionTypes => ({ | ||
type: FenceConnectionsActions.addFenceConnection, | ||
fenceName, | ||
connection, | ||
}); | ||
|
||
const toggleIsFetchingAllFenceConnections = (isLoading: boolean): FenceConnectionsActionTypes => ({ | ||
type: FenceConnectionsActions.toggleIsFetchingAllFenceConnections, | ||
isLoading, | ||
}); | ||
|
||
const shouldFetchConnections = (fenceName: FenceName, state: RootState): boolean => | ||
isEmpty(selectFenceConnections(state)[fenceName]); | ||
|
||
const fetchFencesConnections = ( | ||
api: Api, | ||
fenceName: FenceName, | ||
): ThunkAction<void, RootState, null, FenceConnectionsActionTypes> => async (dispatch) => { | ||
try { | ||
const connection = await getFenceUser(api, fenceName); | ||
dispatch(addFenceConnection(fenceName, connection)); | ||
} catch (error) { | ||
console.error(`Error fetching fence connection for '${fenceName}': ${error}`); | ||
} | ||
}; | ||
|
||
export const fetchFencesConnectionsIfNeeded = ( | ||
api: Api, | ||
fenceName: FenceName, | ||
): ThunkAction<void, RootState, null, FenceConnectionsActionTypes> => async ( | ||
dispatch, | ||
getState, | ||
) => { | ||
if (shouldFetchConnections(fenceName, getState())) { | ||
return dispatch(fetchFencesConnections(api, fenceName)); | ||
} | ||
}; | ||
|
||
export const fetchAllFencesConnectionsIfNeeded = ( | ||
api: Api, | ||
fencesName: FenceName[], | ||
): ThunkAction<void, RootState, null, FenceConnectionsActionTypes> => async (dispatch) => { | ||
dispatch(toggleIsFetchingAllFenceConnections(true)); | ||
for (const fenceName of fencesName) { | ||
await dispatch(fetchFencesConnectionsIfNeeded(api, fenceName)); | ||
} | ||
dispatch(toggleIsFetchingAllFenceConnections(false)); | ||
}; | ||
|
||
export const concatAllFencesAcls = (fenceConnections: FenceConnections) => { | ||
const fenceNames = keys(fenceConnections); | ||
return fenceNames.map((fence: FenceName) => keys(fenceConnections[fence].projects)).flat(); | ||
}; | ||
|
||
export const computeAclsByFence = (fenceConnections: FenceConnections) => | ||
entries(fenceConnections).reduce( | ||
(acc, [fenceName, connection]) => ({ | ||
...acc, | ||
[fenceName]: keys(connection.projects || {}), | ||
}), | ||
{}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import flatMap from 'lodash/flatMap'; | ||
import isEmpty from 'lodash/isEmpty'; | ||
import { ThunkAction } from 'redux-thunk'; | ||
|
||
import { getAuthStudiesIdAndCount, getStudiesCountByNameAndAcl } from 'services/fenceStudies'; | ||
|
||
import { Api } from '../apiTypes'; | ||
import { FenceStudies, FenceStudiesActions, FenceStudiesActionTypes } from '../fenceStudiesTypes'; | ||
import { AclsByFence, FenceName, UserAcls } from '../fenceTypes'; | ||
import { RootState } from '../rootState'; | ||
import { selectFenceStudies } from '../selectors/fenceStudies'; | ||
|
||
const addWildCardToAcls = (acls: UserAcls) => [...(acls || []), '*']; | ||
|
||
const toggleIsFetchingAllFenceStudies = (isLoading: boolean): FenceStudiesActionTypes => ({ | ||
type: FenceStudiesActions.toggleIsFetchingAllFenceStudies, | ||
isLoading, | ||
}); | ||
|
||
const addFenceStudies = (fenceAuthorizedStudies: FenceStudies): FenceStudiesActionTypes => ({ | ||
type: FenceStudiesActions.addFenceStudies, | ||
fenceAuthorizedStudies: fenceAuthorizedStudies, | ||
}); | ||
|
||
const shouldFetchFenceStudies = (fenceName: FenceName, state: RootState) => { | ||
const studiesForFence = selectFenceStudies(state)[fenceName]; | ||
return isEmpty(studiesForFence) || isEmpty(studiesForFence.authorizedStudies); | ||
}; | ||
|
||
const fetchFenceStudies = ( | ||
api: Api, | ||
fenceName: FenceName, | ||
userAcls: UserAcls, | ||
): ThunkAction<void, RootState, null, FenceStudiesActionTypes> => async (dispatch) => { | ||
try { | ||
const aclsWithWildCard = addWildCardToAcls(userAcls); | ||
const studies = await getAuthStudiesIdAndCount(api, fenceName, aclsWithWildCard); | ||
const authorizedStudies = isEmpty(studies) | ||
? [] | ||
: await getStudiesCountByNameAndAcl(api, studies, aclsWithWildCard); | ||
dispatch( | ||
addFenceStudies({ | ||
[fenceName]: { | ||
authorizedStudies: authorizedStudies || [], | ||
}, | ||
}), | ||
); | ||
} catch (error) { | ||
console.error(`Error fetching fence studies for '${fenceName}': ${error}`); | ||
} | ||
}; | ||
|
||
const fetchFenceStudiesIfNeeded = ( | ||
api: Api, | ||
fenceName: FenceName, | ||
aclsByFence: AclsByFence, | ||
): ThunkAction<void, RootState, null, FenceStudiesActionTypes> => async (dispatch, getState) => { | ||
if (shouldFetchFenceStudies(fenceName, getState())) { | ||
const userAcls = aclsByFence[fenceName]; | ||
return dispatch(fetchFenceStudies(api, fenceName, userAcls)); | ||
} | ||
}; | ||
|
||
export const fetchAllFenceStudiesIfNeeded = ( | ||
api: Api, | ||
fencesName: FenceName[], | ||
aclsByFence: AclsByFence, | ||
): ThunkAction<void, RootState, null, FenceStudiesActionTypes> => async (dispatch) => { | ||
dispatch(toggleIsFetchingAllFenceStudies(true)); | ||
for (const fenceName of fencesName) { | ||
await dispatch(fetchFenceStudiesIfNeeded(api, fenceName, aclsByFence)); | ||
} | ||
dispatch(toggleIsFetchingAllFenceStudies(false)); | ||
}; | ||
|
||
export const computeAllFencesAuthStudies = (fenceStudies: FenceStudies) => { | ||
if (isEmpty(fenceStudies)) { | ||
return []; | ||
} | ||
return flatMap(Object.values(fenceStudies), (studies) => studies.authorizedStudies); | ||
}; |
Oops, something went wrong.