Skip to content

Commit

Permalink
Make autocomplete go directly to entity (#458)
Browse files Browse the repository at this point in the history
Closes #456 
Closes #451 
Closes #445 
Closes #439

- "dummy" -> "fake"
- change e2e search tests to match new autocomplete behavior
- rearrange phenogrid response
- refactor autocomplete component to accommodate entity autocomplete
search
- fix input component event bug
- change phenogrid tooltip content
- tweak node history to record node visits instead of searches
- remove overview about page (since it's out of date). see #436 
- fix phenogrid infinite resize loop bug
- refactor router scrollTo logic again, and fix minor bugs
- refactor explore search tab to accommodate auto complete entity search
- fix minor useQuery composable bug
- remove scroll restoration behavior
- fix small tab title has undefined while node name loads
  • Loading branch information
vincerubinetti authored Nov 2, 2023
1 parent 9342d13 commit 029186a
Show file tree
Hide file tree
Showing 24 changed files with 272 additions and 312 deletions.
4 changes: 2 additions & 2 deletions frontend/e2e/phenotype-explorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test("Phenotype set vs gene/disease works", async ({ page }) => {
.first()
.click();

/** paste specific dummy phenotypes */
/** paste specific fake phenotypes */
await paste(page.locator("input"), "HP:0004970,HP:0004933,HP:0004927");

/** run analysis, and look for expected results */
Expand All @@ -87,7 +87,7 @@ test("Phenotype set vs phenotype set works", async ({ page }) => {

await page.goto("/explore#phenotype-explorer");

/** paste specific dummy phenotypes */
/** paste specific fake phenotypes */
await paste(
page.locator("input").first(),
"HP:0004970,HP:0004933,HP:0004927",
Expand Down
56 changes: 27 additions & 29 deletions frontend/e2e/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,59 @@ test("Redirects to explore page from home page", async ({ page }) => {
test("Recent/frequent results show", async ({ page }) => {
await page.goto("/explore");

/** dummy searches */
const searches = [
"abc def",
"123",
"123",
"abc def",
"123",
"123",
"abc def",
const nodes = [
"MONDO:0007523",
"HP:0003179",
"HP:0003179",
"MONDO:0007523",
"HP:0003179",
"HP:0003179",
"MONDO:0007523",
"HP:0100775",
];

/** go through dummy searches */
for (const search of searches) {
await page.locator("input").fill(search);
/** dispatch textbox change event which triggers search and records it */
await page.locator("input").dispatchEvent("change");
/** wait for results */
await expect(page.locator("p.description").first()).toBeVisible();
await page.locator(".textbox button").click();
for (const node of nodes) {
await page.goto("/" + node);
await expect(page.locator("#overview")).toBeVisible();
}

/** go to node page, which should also get added to search history */
await page.goto("/MONDO:12345");
/** wait for page to load */
await expect(page.locator("#overview")).toBeVisible();
await page.goto("/explore");

/** focus search box to show list of results */
await page.locator("input").focus();
const options = page.locator("[role='option']");

/** recent */
const options = page.locator("[role='option']");
await expect(
options
.nth(0)
.getByText(/muscular dystrophy/i)
.getByText(/Dural ectasia/i)
.first(),
).toBeVisible();
await expect(
options
.nth(1)
.getByText(/abc def/i)
.getByText(/Ehlers-Danlos syndrome, hypermobility/i)
.first(),
).toBeVisible();
await expect(
options
.nth(2)
.getByText(/Protrusio acetabuli/i)
.first(),
).toBeVisible();
await expect(options.nth(2).getByText(/123/).first()).toBeVisible();

/** frequent */
await expect(
page.locator("[role='option']").nth(3).getByText(/123/).first(),
page
.locator("[role='option']")
.nth(3)
.getByText(/Protrusio acetabuli/i)
.first(),
).toBeVisible();
await expect(
page
.locator("[role='option']")
.nth(4)
.getByText(/abc def/)
.getByText(/Ehlers-Danlos syndrome, hypermobility/i)
.first(),
).toBeVisible();
});
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/api/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,12 +502,12 @@ export interface TermPairwiseSimilarity extends PairwiseSimilarity {
/** The IC of the object */
ancestor_information_content?: string,
/** The number of concepts in the intersection divided by the number in the union */
jaccard_similarity?: string,
jaccard_similarity?: number,
/** the dot product of two node embeddings divided by the product of their lengths */
cosine_similarity?: number,
dice_similarity?: string,
dice_similarity?: number,
/** the geometric mean of the jaccard similarity and the information content */
phenodigm_score?: string,
phenodigm_score?: number,
};
/**
* A simple pairwise similarity between two sets of concepts/terms
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/api/phenotype-explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,13 @@ export const compareSetToSet = async (
[key: string]: {
score: number;
strength: number;
simInfo: Partial<TermPairwiseSimilarity>;
};
} & Pick<
TermPairwiseSimilarity,
| "ancestor_id"
| "ancestor_label"
| "jaccard_similarity"
| "phenodigm_score"
>;
} = {};

/** get subject matches */
Expand All @@ -162,8 +167,9 @@ export const compareSetToSet = async (
cells[col.id + row.id] = {
score: match?.score || 0,
strength: 0,
simInfo: pick(match?.similarity, [
...pick(match?.similarity, [
"ancestor_id",
"ancestor_label",
"jaccard_similarity",
"phenodigm_score",
]),
Expand Down
23 changes: 3 additions & 20 deletions frontend/src/api/search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { groupBy, uniq } from "lodash";
import type { SearchResult, SearchResults } from "@/api/model";
import type { SearchResults } from "@/api/model";
import { apiUrl, request } from "./index";

export type Filters = { [key: string]: string[] };
Expand All @@ -21,29 +20,13 @@ export const getSearch = async (
return response;
};

type DedupedSearchResults = Omit<SearchResults, "items"> & {
items: (SearchResult & { dupes: string[] })[];
};

export const getAutocomplete = async (q: string) => {
const url = `${apiUrl}/autocomplete`;
const response = await request<SearchResults>(url, { q });

const transformedResponse: DedupedSearchResults = {
const transformedResponse = {
...response,
items: Object.values(
/** consolidate items */
groupBy(
response.items,
/** by name, case insensitively */
(item) => item.name.toLowerCase(),
),
).map((dupes) => ({
...dupes[0],
/** keep list of duplicated names */
/** de-dupe this list case sensitively */
dupes: uniq(dupes.map((dupe) => dupe.name)),
})),
items: response.items.slice(0, 20),
};

return transformedResponse;
Expand Down
45 changes: 26 additions & 19 deletions frontend/src/components/AppSelectAutocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,14 @@
:id="`option-${id}-${index}`"
:key="index"
v-tooltip="option.tooltip"
:class="['option', { highlighted: index === highlighted }]"
:class="[
'option',
{ highlighted: index === highlighted, special: option.special },
]"
role="option"
:aria-selected="true"
tabindex="0"
@click.prevent="() => select(option.label)"
@click.prevent="() => select(option)"
@mousedown.prevent=""
@focusin="() => null"
@keydown="() => null"
Expand All @@ -83,9 +86,6 @@
</div>
</div>
</Teleport>

<!-- description -->
<div v-if="description" class="description">{{ description }}</div>
</div>
</template>

Expand All @@ -99,12 +99,16 @@ export type Option = {
icon?: string;
/** display label */
label: string;
/** unique id */
id?: string;
/** highlighting html */
highlight?: string;
/** info col */
info?: string;
/** tooltip on hover */
tooltip?: string;
/** whether option is "special" (gets styled differently) */
special?: boolean;
};
</script>

Expand Down Expand Up @@ -136,9 +140,9 @@ type Emits = {
/** when input focused */
focus: [];
/** when input value change "submitted"/"committed" by user */
change: [string];
change: [string | Option, string];
/** when user wants to delete an entry */
delete: [string];
delete: [Option];
};

const emit = defineEmits<Emits>();
Expand Down Expand Up @@ -182,9 +186,13 @@ async function onDebounce() {
await runGetResults();
}

/** ignore next child input box change event */
let ignoreChange = false;

/** when user "commits" change to value, e.g. pressing enter, de-focusing, etc */
function onChange(value: string) {
select(value);
async function onChange(value: string) {
if (!ignoreChange) select(value);
ignoreChange = false;
}

/** when user presses key in input */
Expand All @@ -210,13 +218,12 @@ async function onKeydown(event: KeyboardEvent) {

/** enter key to select highlighted result */
if (event.key === "Enter" && highlighted.value >= 0) {
event.stopPropagation();
select(results.value[highlighted.value].label);
select(results.value[highlighted.value]);
}

/** delete key to delete the highlighted result */
if (event.key === "Delete" && event.shiftKey) {
emit("delete", results.value[highlighted.value].label);
emit("delete", results.value[highlighted.value]);
await runGetResults();
}

Expand All @@ -225,9 +232,11 @@ async function onKeydown(event: KeyboardEvent) {
}

/** select an option */
async function select(value: string) {
search.value = value;
emit("change", value);
async function select(value: string | Option) {
/** ignore next child input box change event triggered by enter press */
ignoreChange = true;
emit("change", value, search.value);
search.value = typeof value === "string" ? value : value.label;
close();
}

Expand Down Expand Up @@ -345,9 +354,7 @@ watch(highlighted, () => {
color: $gray;
}

.description {
margin-top: 10px;
color: $dark-gray;
font-size: 0.9rem;
.special {
font-weight: 500;
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/components/AppTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ watch(
}

&.divider {
width: 2px;
width: 2px !important;
margin: 0 auto;
padding: 0;
background: $light-gray;
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/components/AppTextbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type Emits = {
blur: [];
};

const emit = defineEmits<Emits>();
defineEmits<Emits>();

/** element reference */
const textbox = ref();
Expand All @@ -91,7 +91,7 @@ const input = ref();
function clear() {
input.value.input.value = "";
input.value.input.dispatchEvent(new Event("input"));
emit("change", "");
input.value.input.dispatchEvent(new Event("change"));
}

/** allow parent to access ref */
Expand Down Expand Up @@ -141,7 +141,6 @@ $height: 40px;
justify-content: center;
width: $height;
height: $height;
color: $gray;
}

.input {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/TheHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ $wrap: 900px;
.nav {
display: flex;
align-items: center;
justify-content: center;
max-width: 100%;
justify-content: flex-end;
width: 100%;
padding: 15px;
gap: 10px;
}
Expand Down
Loading

0 comments on commit 029186a

Please sign in to comment.