-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
52 changed files
with
166,548 additions
and
205 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
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,19 @@ | ||
import { Controller, Get, Logger, Query, Res } from '@nestjs/common'; | ||
import { Response } from 'express'; | ||
import { VeradigmService } from './veradigm.service'; | ||
|
||
@Controller('v1/veradigm') | ||
export class VeradigmController { | ||
constructor(private readonly veradigmService: VeradigmService) {} | ||
|
||
@Get('tenants') | ||
async getData(@Res() response: Response, @Query('query') query) { | ||
try { | ||
const data = await this.veradigmService.queryTenants(query); | ||
response.json(data); | ||
} catch (e) { | ||
Logger.log(e); | ||
response.status(500).send({ message: 'There was an error' }); | ||
} | ||
} | ||
} |
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,9 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { VeradigmService } from './veradigm.service'; | ||
import { VeradigmController } from './veradigm.controller'; | ||
|
||
@Module({ | ||
controllers: [VeradigmController], | ||
providers: [VeradigmService], | ||
}) | ||
export class VeradigmModule {} |
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,82 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { VeradigmDSTU2TenantEndpoints, DSTU2Endpoint } from '@mere/veradigm'; | ||
|
||
@Injectable() | ||
export class VeradigmService { | ||
private readonly items = VeradigmDSTU2TenantEndpoints; | ||
|
||
async queryTenants(query: string): Promise<DSTU2Endpoint[]> { | ||
return filteredItemsWithQuery(this.items, query); | ||
} | ||
} | ||
|
||
function filteredItemsWithQuery(items: DSTU2Endpoint[], query: string) { | ||
if (query === '' || query === undefined) { | ||
return items | ||
.filter( | ||
(item) => | ||
!!item.name?.trim() && | ||
!!item.authorize?.trim() && | ||
!!item.token?.trim() | ||
) | ||
.sort((x, y) => (x.name > y.name ? 1 : -1)) | ||
.slice(0, 100); | ||
} | ||
return items | ||
.filter( | ||
(item) => | ||
!!item.name?.trim() && !!item.authorize?.trim() && !!item.token?.trim() | ||
) | ||
.map((item) => { | ||
// Match against each token, take highest score | ||
const vals = item.name | ||
.split(' ') | ||
.map((token) => stringSimilarity(token, query)); | ||
const rating = Math.max(...vals); | ||
return { rating, item }; | ||
}) | ||
.filter((item) => item.rating > 0.05) | ||
.sort((a, b) => b.rating - a.rating) | ||
.slice(0, 50) | ||
.map((item) => item.item); | ||
} | ||
|
||
/** | ||
* Compares the similarity between two strings using an n-gram comparison method. | ||
* The grams default to length 2. | ||
* @param str1 The first string to compare. | ||
* @param str2 The second string to compare. | ||
* @param gramSize The size of the grams. Defaults to length 2. | ||
*/ | ||
export function stringSimilarity(str1: string, str2: string, gramSize = 2) { | ||
if (!str1?.length || !str2?.length) { | ||
return 0.0; | ||
} | ||
|
||
//Order the strings by length so the order they're passed in doesn't matter | ||
//and so the smaller string's ngrams are always the ones in the set | ||
const s1 = str1.length < str2.length ? str1 : str2; | ||
const s2 = str1.length < str2.length ? str2 : str1; | ||
|
||
const pairs1 = getNGrams(s1, gramSize); | ||
const pairs2 = getNGrams(s2, gramSize); | ||
const set = new Set<string>(pairs1); | ||
|
||
const total = pairs2.length; | ||
let hits = 0; | ||
for (const item of pairs2) { | ||
if (set.delete(item)) { | ||
hits++; | ||
} | ||
} | ||
return hits / total; | ||
} | ||
|
||
function getNGrams(s: string, len: number) { | ||
s = ' '.repeat(len - 1) + s.toLowerCase() + ' '.repeat(len - 1); | ||
const v = new Array(s.length - len + 1); | ||
for (let i = 0; i < v.length; i++) { | ||
v[i] = s.slice(i, i + len); | ||
} | ||
return v; | ||
} |
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
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
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
10 changes: 8 additions & 2 deletions
10
apps/web/src/components/connection/ButtonLoadingSpinner.tsx
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
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
118 changes: 118 additions & 0 deletions
118
apps/web/src/components/connection/VeradigmSelectModal.tsx
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,118 @@ | ||
import { memo, useEffect, useState } from 'react'; | ||
import { Combobox } from '@headlessui/react'; | ||
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'; | ||
import { ExclamationCircleIcon } from '@heroicons/react/24/outline'; | ||
import { useDebounce } from '@react-hook/debounce'; | ||
import { Modal } from '../Modal'; | ||
import { ModalHeader } from '../ModalHeader'; | ||
import { SelectOption } from '../../pages/ConnectionTab'; | ||
import { DSTU2Endpoint } from '@mere/cerner'; | ||
import { CernerSelectModelResultItem } from './CernerSelectModalItem'; | ||
import { useNotificationDispatch } from '../providers/NotificationProvider'; | ||
|
||
export function VeradigmSelectModal({ | ||
open, | ||
setOpen, | ||
onClick, | ||
}: { | ||
open: boolean; | ||
setOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
onClick: ( | ||
base: string & Location, | ||
auth: string & Location, | ||
token: string & Location, | ||
name: string, | ||
id: string | ||
) => void; | ||
}) { | ||
const [query, setQuery] = useDebounce('', 150), | ||
[items, setItems] = useState<DSTU2Endpoint[]>([]), | ||
notifyDispatch = useNotificationDispatch(); | ||
|
||
useEffect(() => { | ||
const abortController = new AbortController(); | ||
|
||
fetch(`/api/v1/veradigm/tenants?` + new URLSearchParams({ query }), { | ||
signal: abortController.signal, | ||
}) | ||
.then((x) => x.json()) | ||
.then((x) => setItems(x)) | ||
.catch(() => { | ||
notifyDispatch({ | ||
type: 'set_notification', | ||
message: `Unable to search for health systems`, | ||
variant: 'error', | ||
}); | ||
}); | ||
|
||
return () => { | ||
abortController.abort(); | ||
}; | ||
}, [notifyDispatch, query]); | ||
|
||
return ( | ||
<Modal | ||
open={open} | ||
setOpen={setOpen} | ||
afterLeave={() => setQuery('')} | ||
overflowHidden | ||
> | ||
<ModalHeader | ||
title={'Select your Veradigm health system to log in'} | ||
setClose={() => setOpen((x) => !x)} | ||
/> | ||
<Combobox | ||
onChange={(s: SelectOption) => { | ||
onClick(s.baseUrl, s.authUrl, s.tokenUrl, s.name, s.id); | ||
setOpen(false); | ||
}} | ||
> | ||
<div className="relative px-4"> | ||
<MagnifyingGlassIcon | ||
className="pointer-events-none absolute top-3.5 left-8 h-5 w-5 text-gray-400" | ||
aria-hidden="true" | ||
/> | ||
<Combobox.Input | ||
className="focus:ring-primary-700 h-12 w-full divide-y-2 rounded-xl border-0 bg-gray-50 bg-transparent pl-11 pr-4 text-gray-800 placeholder-gray-400 hover:border-gray-200 focus:ring-2 sm:text-sm" | ||
placeholder="Search for your health system" | ||
onChange={(event) => setQuery(event.target.value)} | ||
autoFocus={true} | ||
/> | ||
</div> | ||
{items.length > 0 && ( | ||
<Combobox.Options | ||
static | ||
className="max-h-full scroll-py-3 overflow-y-scroll p-3 sm:max-h-96" | ||
> | ||
{items.map((item) => ( | ||
<MemoizedCernerResultItem | ||
key={item.id} | ||
id={item.id} | ||
name={item.name} | ||
baseUrl={item.url} | ||
tokenUrl={item.token} | ||
authUrl={item.authorize} | ||
/> | ||
))} | ||
</Combobox.Options> | ||
)} | ||
|
||
{query !== '' && items.length === 0 && ( | ||
<div className="py-14 px-6 text-center text-sm sm:px-14"> | ||
<ExclamationCircleIcon | ||
type="outline" | ||
name="exclamation-circle" | ||
className="mx-auto h-6 w-6 text-gray-400" | ||
/> | ||
<p className="mt-4 font-semibold text-gray-900">No results found</p> | ||
<p className="mt-2 text-gray-500"> | ||
No health system found for this search term. Please try again. | ||
</p> | ||
</div> | ||
)} | ||
</Combobox> | ||
</Modal> | ||
); | ||
} | ||
|
||
export const MemoizedCernerResultItem = memo(CernerSelectModelResultItem); |
Oops, something went wrong.