Skip to content

Commit

Permalink
Merge pull request #980 from CityOfBoston/DIG-3552
Browse files Browse the repository at this point in the history
DIG-2690/3552/3684: Group-Mgmt App Improvements
  • Loading branch information
phillipbentonkelly authored Dec 7, 2023
2 parents 009e81a + 596cf3e commit 145814b
Show file tree
Hide file tree
Showing 43 changed files with 1,347 additions and 946 deletions.
2 changes: 2 additions & 0 deletions services-js/access-boston/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ service-provider.crt
service-provider.key
/pingid.properties
/apps.yaml

*.env
10 changes: 5 additions & 5 deletions services-js/access-boston/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ CMD ["yarn", "start"]
# To use this file:
# [a] clone the digital repo
# [b] from a terminal, in the access-boston folder, run
# DOCKER_BUILDKIT=1 docker build --pull --cache-from local-test/access-boston:latest -f ./Dockerfile -t local-test/access-boston:latest --secret id=aws,src=$HOME/.aws/credentials ../..
# DOCKER_BUILDKIT=1 docker build --pull --cache-from local-test/access-boston:latest -f ./Dockerfile -t local-test/access-boston:latest --secret id=aws,src=$HOME/.aws/credentials --platform linux/amd64 ../..
# -> this will create an image tagged local-test/access-boston:latest on the local machine
# [c] from a terminal, in the access-boston folder, run
# docker compose up --no-build -d access-boston
# -> this will create a container which should start on your local machine, with your cloned repo mounted into
# so that your changes are immediately effective in the browser.
# -> you should be able to see the webapp at https://127.0.0.1:300/group-mgmt ..etc
# -> you should be able to see the webapp at https://127.0.0.1:3000/group-mgmt ..etc
# [d] In a terminal app, open a session in the container by running:
# docker exec -it access-boston /bin/sh
# -> This has effectively ssh'd you into the container as the defaul (root) user.
Expand All @@ -107,17 +107,17 @@ CMD ["yarn", "start"]
# docker stop access-boston
# [2.5] Re-login into ECR with this command --> aws ecr get-login-password --region us-east-1 --profile=cityofboston | docker login --username AWS --password-stdin 251803681989.dkr.ecr.us-east-1.amazonaws.com
# [3] Rebuild the container and apps by running:
# DOCKER_BUILDKIT=1 docker build --pull --cache-from local-test/access-boston:latest -f ./Dockerfile -t 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-staging/access-boston:deploy-new-stage --secret id=aws,src=$HOME/.aws/credentials ../..
# DOCKER_BUILDKIT=1 docker build --pull --cache-from local-test/access-boston:latest -f ./Dockerfile -t 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-staging/access-boston:deploy-new-stage-test --secret id=aws,src=$HOME/.aws/credentials --platform linux/amd64 ../..
# -> this is essentially the same command as in [b] above, just uses a different tag and ensures the /app folder
# is physically there and not mounted (using the docker-compose command in [c] above mounts the repo over
# whatever was added into the the image's /app folder during the docker build command)
# [4] Push the image to AWS by running
# docker push 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-staging/access-boston:deploy-new-stage
# docker push 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-staging/access-boston:deploy-new-stage-test
# -> the deploy should start once the image is transferred/uploaded
#
# WHEN YOU ARE READY TO DEPLOY TO PROD:
# [1] Tag the image you pushed to stage with a production tag:
# docker tag 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-staging/access-boston:deploy-new-stage 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-prod/access-boston:deploy-new-prod
# docker tag 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-staging/access-boston:deploy-new-stage-test 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-prod/access-boston:deploy-new-prod
# [2] Push the image to AWS by running
# docker push 251803681989.dkr.ecr.us-east-1.amazonaws.com/cob-digital-apps-prod/access-boston:deploy-new-prod
# -> the deploy should start once the image is transferred/uploaded
9 changes: 8 additions & 1 deletion services-js/access-boston/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
version: '3'
services:
access-boston:
# EXTRA_HOSTS=group-mgmt-test.digital-staging.boston.gov:172.17.0.3
# COMPOSE_PORTS=3001:3001
# AWS_S3_CONFIG_URL=s3://cob-digital-apps-staging-config/access-boston
# image: arm64/local-test/access-boston:latest
image: local-test/access-boston:latest
container_name: access-boston
ports:
- 4000:3000
- 3001:3001
# - ${COMPOSE_PORTS}
working_dir: /app/services-js/access-boston
extra_hosts:
- group-mgmt-test.digital-staging.boston.gov:172.17.0.3
# - ${EXTRA_HOSTS}
volumes:
- ~/.aws:/root/.aws
environment:
AWS_S3_CONFIG_URL: s3://cob-digital-apps-staging-config/access-boston
# AWS_S3_CONFIG_URL: ${AWS_S3_CONFIG_URL}
DEPLOY_VARIANT: test
AWS_DEFAULT_REGION: us-east-1
AWS_REGION: us-east-1
Expand Down
2 changes: 1 addition & 1 deletion services-js/access-boston/fixtures/apps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ categories:
- SG_AB_HCMSECURITY
- title: Group Management
url: /group-management
target: _blank
# target: _blank

- title: Manager Tools
apps:
Expand Down
195 changes: 77 additions & 118 deletions services-js/access-boston/src/client/group-management/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,17 @@ import InitialView from './InitialView';
import ManagementView from './ManagementView';
import ReviewChangesView from './ReviewChangesView';
import ReviewConfirmationView from './ReviewConfirmationView';

import EditableList from './list-components/EditableList';
import SearchComponent from './search-component/SearchComponent';
import EditView from './list-components/edit';

import {
fetchGroupSearch,
fetchGroupSearchRemaining,
fetchPersonsGroups,
fetchOurContainers,
fetchDataURL,
fetchMinimumUserGroups,
} from './data-fetching/fetch-group-data';
import {
fetchGroupMembers,
fetchPersonSearch,
fetchPersonSearchRemaining,
} from './data-fetching/fetch-person-data';
Expand Down Expand Up @@ -58,7 +55,26 @@ export default function Index(props: Props) {
const changeMode = (newMode: Mode): void =>
dispatchState({ type: 'APP/CHANGE_MODE', mode: newMode });

const changeSelected = (selectedItem: Group | Person): void => {
const changePageCount = (pageCount: number): void => {
dispatchState({ type: 'APP/CHANGE_PAGECOUNT', pageCount });
};

const handleClickListItem = (item: Group | Person): void => {
changeSelected(item);
changeMode(state.mode === 'group' ? 'person' : 'group');
};

const handleToggleItem = (item: Group | Person) => {
if (item.action && item.action === 'new') {
dispatchList({ type: 'LIST/DELETE_ITEM', item });
} else {
dispatchList({ type: 'LIST/TOGGLE_ITEM_STATUS', item });
}
};

const changeSelected = async (
selectedItem: Group | Person
): Promise<void> => {
dispatchState({ type: 'APP/SET_SELECTED', selected: selectedItem });
};

Expand All @@ -69,38 +85,17 @@ export default function Index(props: Props) {

const changePage = (currentPage: number): void => {
dispatchState({ type: 'APP/CHANGE_PAGE', currentPage });
const { mode, selected } = state;
if (mode === 'group') {
if (
selected.cn &&
selected.chunked[currentPage] &&
selected.chunked[currentPage].length > 0
)
handleFetchGroupMembers(selected, groups, currentPage);
} else {
if (
selected.cn &&
selected.chunked[currentPage] &&
selected.chunked[currentPage].length > 0
)
handleFetchPersonsGroups(selected, groups, currentPage);
}
};

const changePageCount = (pageCount: number): void => {
dispatchState({ type: 'APP/CHANGE_PAGECOUNT', pageCount });
dispatchList({
type: 'LIST/LOAD_LIST',
list: state.selected.groupmember[currentPage],
});
};

const handleInitialSelection = (selectedItem: any): void => {
changeSelected(selectedItem);
changeView('management');
};

const handleClickListItem = (item: Group | Person): void => {
changeSelected(item);
changeMode(state.mode === 'group' ? 'person' : 'group');
};

const handleAdminListItemClick = (item: any): void => {
changeSelected(item);
changeView('management');
Expand All @@ -119,88 +114,44 @@ export default function Index(props: Props) {

const setOus = async () => {
fetchOurContainers(groups).then(result => {
dispatchState({
type: 'APP/SET_OUS',
ous: result.convertOUsToContainers,
});
if (result !== null) {
dispatchState({
type: 'APP/SET_OUS',
ous: result.convertOUsToContainers,
});
}
});
};

const getAdminMinGroups = async () => {
fetchMinimumUserGroups(groups).then(result => {
let ret = result.getMinimumUserGroups.map((entry: Group | Person) => {
let remappedObj = renameObjectKeys(
{ uniquemember: 'members', memberof: 'members' },
entry
);
remappedObj['chunked'] =
remappedObj['members'] && remappedObj['members'].length > -1
? chunkArray(remappedObj['members'], pageSize)
: [];
remappedObj['isAvailable'] = true;
remappedObj['status'] = 'current';

return remappedObj;
});

dispatchState({
type: 'APP/SET_ADMIN_MIN_GROUPS',
dns: ret,
});
});
};

const handleFetchGroupMembers = (
selected: Group,
dns: String[] = [],
_currentPage: number = 0
): void => {
const { members, chunked } = selected;
const mask_chunked = chunked ? chunked : [];
changePageCount(mask_chunked.length);

if (members && members.length > 0) {
setLoading(true);
fetchGroupMembers(selected, dns, _currentPage).then(result => {
dispatchList({
type: 'LIST/LOAD_LIST',
list: result,
// console.log('fetchMinimumUserGroups');
if (result !== null && result.getMinimumUserGroups !== null) {
// console.log('fetchMinimumUserGroups > results');
let ret = result.getMinimumUserGroups.map((entry: Group | Person) => {
// console.log('fetchMinimumUserGroups > results > map(entry): ', entry);
let remappedObj = renameObjectKeys(
{ uniquemember: 'members', memberof: 'members', member: 'members' },
entry
);
remappedObj['chunked'] =
remappedObj['members'] && remappedObj['members'].length > -1
? chunkArray(remappedObj['members'], pageSize)
: [];
remappedObj['isAvailable'] = true;
remappedObj['status'] = 'current';
return remappedObj;
});

setLoading(false);
});
}
};

const handleFetchPersonsGroups = (
selected: Person,
dns: string[] = [],
_currentPage: number = 0
): void => {
const { groups } = selected;

if (groups && groups.length > 0) {
setLoading(true);
fetchPersonsGroups(selected, [], dns, state.ous, _currentPage).then(
result => {
dispatchList({
type: 'LIST/LOAD_LIST',
list: result,
});
setLoading(false);
}
);
}
dispatchState({
type: 'APP/SET_ADMIN_MIN_GROUPS',
dns: ret,
});
}
});
};

useEffect(() => {
const { mode, selected } = state;
if (!viewOnly && mode === 'group') {
if (selected.cn) handleFetchGroupMembers(selected, groups);
} else {
if (selected.cn) handleFetchPersonsGroups(selected, groups);
}

// Update the document title using the browser API
if (
!groups ||
Expand All @@ -223,14 +174,6 @@ export default function Index(props: Props) {
}
}, [state.selected]);

const handleToggleItem = (item: Group | Person) => {
if (item.action && item.action === 'new') {
dispatchList({ type: 'LIST/DELETE_ITEM', item });
} else {
dispatchList({ type: 'LIST/TOGGLE_ITEM_STATUS', item });
}
};

const handleAddToList = (item: Group | Person) =>
dispatchList({ type: 'LIST/ADD_ITEM', item });

Expand Down Expand Up @@ -274,23 +217,29 @@ export default function Index(props: Props) {
handleSelectClick={handleAddToList}
selectedItem={state.selected}
dns={groups}
ous={state.ous}
cnArray={cnEntries}
currentlist={list}
pageSize={state.pageSize}
setLoading={setLoading}
dispatchState={dispatchState}
dispatchList={dispatchList}
currentPage={state.currentPage}
changePageCount={changePageCount}
/>
}
editableList={
<EditableList
editView={
<EditView
mode={viewOnly ? 'person' : state.mode}
items={list}
loading={loading}
handleChange={handleToggleItem}
handleClick={handleClickListItem}
dns={groups}
currentPage={state.currentPage}
pageCount={state.pageCount}
pageSize={state.pageSize}
changePage={changePage}
currentPage={state.currentPage}
viewOnly={viewOnly && viewOnly === true ? true : false}
changePage={changePage}
handleChange={handleToggleItem}
handleClick={handleClickListItem}
list={list}
/>
}
resetAll={resetAll}
Expand Down Expand Up @@ -348,8 +297,18 @@ export default function Index(props: Props) {
}
handleSelectClick={handleInitialSelection}
dns={groups}
ous={state.ous}
pageSize={state.pageSize}
setLoading={setLoading}
dispatchState={dispatchState}
dispatchList={dispatchList}
currentPage={state.currentPage}
changePageCount={changePageCount}
/>
}
pageSize={state.pageSize}
dispatchList={dispatchList}
changePageCount={changePageCount}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { Mode } from './types';
import Section from './Section';

import Icon from './Icon';
// import { Group, Person } from '../group-management/types';
import MinGroupDisplay from './MinGroupDisplay';

interface Props {
Expand All @@ -29,6 +28,9 @@ interface Props {
adminMinGroups?: [];
handleAdminGroupClick: (item: any) => void;
viewOnly?: boolean;
pageSize: number;
dispatchList: any;
changePageCount: any;
}

/**
Expand Down Expand Up @@ -82,6 +84,9 @@ export default function InitialView(props: Props) {
<MinGroupDisplay
groups={admin_groups}
handleAdminGroupClick={props.handleAdminGroupClick}
pageSize={props.pageSize}
dispatchList={props.dispatchList}
changePageCount={props.changePageCount}
/>
) : (
<>{props.searchComponent}</>
Expand Down
Loading

0 comments on commit 145814b

Please sign in to comment.