Skip to content

Commit

Permalink
Add standalone/native negative news view (#134)
Browse files Browse the repository at this point in the history
Signed-off-by: Sean Sundberg <[email protected]>
  • Loading branch information
seansund authored Oct 11, 2023
1 parent cc1d11a commit 1011738
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
KYCCaseDetail,
KYCCaseList,
KycSummarize,
NegativeNewsView,
RTCQC,
Utilities,
Welcome
Expand Down Expand Up @@ -48,6 +49,7 @@ function App() {
{title: 'RTC - QC', href: '/secure/rtc-qc', element: <RTCQC /> },
{title: 'KYC Summarization', href: '/secure/kyc-summarization', element: <KycSummarize /> },
{title: 'KYC Summarize', href: '/secure/kyc-summarize', excludeFromMenu: true, element: <KycSummarizeNative returnUrl="/secure/kyc-summarization" /> },
{title: 'Negative News', href: '/secure/negative-news', excludeFromMenu: true, element: <NegativeNewsView returnUrl="/secure/utilities" /> },
{
title: 'Utilities',
href: '/secure/utilities',
Expand Down
1 change: 1 addition & 0 deletions src/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './dashboard.atom';
export * from './current-user.atom';
export * from './navigation.atom';
export * from './kyc-case-summary.atom';
export * from './negative-news.atom';
20 changes: 20 additions & 0 deletions src/atoms/negative-news.atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {atom} from "jotai";
import {loadable} from "jotai/utils";

import {isPersonModel, NegativeScreeningModel, PersonModel} from "../models";
import {negativeNewsApi} from "../services/negative-news";

const baseAtom = atom<Promise<NegativeScreeningModel>>(Promise.resolve(undefined))

export const negativeScreeningAtom = atom(
get => get(baseAtom),
(_, set, param: PersonModel | Promise<NegativeScreeningModel>) => {
const value: Promise<NegativeScreeningModel> = isPersonModel(param)
? negativeNewsApi().screenPerson(param)
: param;

set(baseAtom, value);
}
)

export const negativeScreeningLoadable = loadable(negativeScreeningAtom)
5 changes: 5 additions & 0 deletions src/models/kyc-case.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export interface KycCaseModel {
export interface PersonModel {
name: string;
countryOfResidence: string;
dateOfBirth?: string;
}

export const isPersonModel = (val: unknown): val is PersonModel => {
return !!val && !!(val as PersonModel).name && !!(val as PersonModel).countryOfResidence;
}

export interface CustomerModel extends PersonModel {
Expand Down
1 change: 1 addition & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './menu-options';
export * from './data-extraction';
export * from './file-upload';
export * from './login';
export * from './negative-news';
13 changes: 13 additions & 0 deletions src/services/negative-news/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {NegativeNewsApi} from "./negative-news.api";
import {NegativeNewsGraphql} from "./negative-news.graphql";

export * from './negative-news.api'

let _instance: NegativeNewsApi
export const negativeNewsApi = (): NegativeNewsApi => {
if (_instance) {
return _instance
}

return _instance = new NegativeNewsGraphql();
}
7 changes: 7 additions & 0 deletions src/services/negative-news/negative-news.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {NegativeScreeningModel, PersonModel} from "../../models";

export abstract class NegativeNewsApi {
abstract screenPerson(person: PersonModel): Promise<NegativeScreeningModel>;
abstract validateUrl<T extends { link: string }, R extends T & { isValid: boolean }>(data: T): Promise<R>;
}

88 changes: 88 additions & 0 deletions src/services/negative-news/negative-news.graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {ApolloClient, gql} from "@apollo/client";

import {NegativeNewsApi} from "./negative-news.api";
import {getApolloClient} from "../../backends";
import {NegativeScreeningModel, PersonModel} from "../../models";

const SCREEN_PERSON = gql`
query ScreenPerson($country: String, $dateOfBirth: String, $name: String!) {
screenNews(name: $name, dateOfBirth: $dateOfBirth, country: $country) {
error
nonNegativeNewsCount
subject
summary
totalScreened
unrelatedNewsCount
negativeNewsCount
negativeNews {
date
hasNegativeNews
link
negativeNewsTopics
snippet
source
summary
title
}
nonNegativeNews {
date
hasNegativeNews
link
negativeNewsTopics
snippet
source
summary
title
}
unrelatedNews {
date
hasNegativeNews
link
negativeNewsTopics
snippet
source
summary
title
}
}
}
`

const VALIDATE_URL = gql`
query ValidateUrl($url: String!) {
validateUrl(url: $url) {
isValid
}
}
`

export class NegativeNewsGraphql implements NegativeNewsApi {
client: ApolloClient<unknown>;

constructor() {
this.client = getApolloClient();
}

screenPerson(person: PersonModel): Promise<NegativeScreeningModel> {
return this.client
.query<{screenNews: NegativeScreeningModel}>({
query: SCREEN_PERSON,
variables: {
name: person.name,
country: person.countryOfResidence,
dateOfBirth: person.dateOfBirth
}
})
.then(result => result.data.screenNews)
}

validateUrl<T extends { link: string }, R extends T & { isValid: boolean }>(data: T): Promise<R> {
return this.client
.query<{validateUrl: {isValid: boolean, link: string}}>({
query: VALIDATE_URL,
variables: {url: data.link}
})
.then(result => Object.assign({}, data, result.data.validateUrl)) as any
}

}
65 changes: 65 additions & 0 deletions src/views/NegativeNewsView/NegativeNewsInputs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import React, {useState} from 'react';
import {Button, Form, TextInput} from "@carbon/react";
import {CountrySelect, Stack} from "../../components";
import {FormOptionModel, PersonModel} from "../../models";
import {useNavigate} from "react-router-dom";

export interface NegativeNewsInputsProps {
party: PersonModel;
returnUrl: string;
onSubmit: (subject: PersonModel) => void;
}

export const NegativeNewsInputs: React.FunctionComponent<NegativeNewsInputsProps> = (props: NegativeNewsInputsProps) => {
const navigate = useNavigate();
const [party, setParty] = useState<PersonModel>(props.party)

const setValue = (key: keyof PersonModel, value: string) => {
const newValue: Partial<PersonModel> = {}

newValue[key] = value

setParty(Object.assign({}, party, newValue))
}

const handleSubmit = (event) => {
event.preventDefault();
event.stopPropagation();

props.onSubmit(party);
}

const handleCancel = () => {
navigate(props.returnUrl)
}

return (
<Form onSubmit={handleSubmit}>
<div style={{textAlign: 'left'}}>
<Stack gap={3}>
<h2>Negative News Screening</h2>
<TextInput
id="negativeNewsSubject"
invalidText="Invalid party name"
labelText="Party name"
placeholder="Party name"
value={party.name}
onChange={event => setValue('name', event.target.value)}
required={true}
/>
<CountrySelect
id="negativeNewsCountry"
value={party.countryOfResidence}
onChange={(data: {selectedItem: FormOptionModel}) => {
setValue('countryOfResidence', data.selectedItem?.value || '')
}}
required={true}
/>
<div><Button onClick={handleCancel} kind="tertiary">Cancel</Button> <Button type="submit">Submit</Button></div>
</Stack>
</div>
</Form>
)
}
36 changes: 36 additions & 0 deletions src/views/NegativeNewsView/NegativeNewsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import React, {useState} from 'react';
import {Loading} from "@carbon/react";
import {useAtomValue, useSetAtom} from "jotai";

import {NegativeNewsInputs} from "./NegativeNewsInputs";
import {negativeScreeningAtom, negativeScreeningLoadable} from "../../atoms";
import {NegativeNews, Stack} from "../../components";
import {NegativeScreeningModel, PersonModel} from "../../models";
import {negativeNewsApi} from "../../services/negative-news";

export interface NegativeNewsViewProps {
returnUrl: string;
}

export const NegativeNewsView: React.FunctionComponent<NegativeNewsViewProps> = (props: NegativeNewsViewProps) => {
const [party, setParty] = useState<PersonModel>({name: '', countryOfResidence: 'United States', dateOfBirth: ''})
const loadable = useAtomValue(negativeScreeningLoadable)
const setNews = useSetAtom(negativeScreeningAtom)

const handleSubmit = (newSubject: PersonModel) => {
setNews(negativeNewsApi().screenPerson(newSubject));
setParty(newSubject)
}

const news: NegativeScreeningModel = (loadable as {state: string, data: NegativeScreeningModel}).data
return (
<Stack gap={5}>
<NegativeNewsInputs returnUrl={props.returnUrl} party={party} onSubmit={handleSubmit} />
{loadable.state === 'loading' ? <Loading active={true} description="Loading negative news" id="negative-news-loading" withOverlay={false} /> : <></>}
{loadable.state === 'hasError' ? <div>Error: {loadable.error.toString()}</div> : <></>}
{news ? <NegativeNews type="Party" subject={party.name} news={news} hideTitle={true} /> : <></>}
</Stack>
)
}
1 change: 1 addition & 0 deletions src/views/NegativeNewsView/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './NegativeNewsView.tsx'
1 change: 1 addition & 0 deletions src/views/Utilities/Utilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const Utilities: React.FunctionComponent<UtilitiesProps> = () => {
return (
<DemoTileContainer>
<DemoTile title="Data Extraction and Population" href="/secure/utilities/data-extraction" />
<DemoTile title="Negative News Screening (Native)" href="/secure/negative-news" />
<DemoTile title="Negative News Screening" href={menuConfig.negativeNewsScreeningUrl} />
</DemoTileContainer>
)
Expand Down
1 change: 1 addition & 0 deletions src/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './KycSummarize'
export * from './Login'
export * from './DataExtraction'
export * from './Welcome'
export * from './NegativeNewsView'

0 comments on commit 1011738

Please sign in to comment.