Skip to content

Commit

Permalink
feat: knn project search ui (#5651)
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnyama authored Nov 29, 2024
1 parent 959c5e3 commit 3762825
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 124 deletions.
172 changes: 148 additions & 24 deletions packages/client/hmi-client/src/components/home/tera-project-table.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
<template>
<DataTable :value="projects" dataKey="id" :rowsPerPageOptions="[10, 20, 50]" scrollable scrollHeight="45rem">
<!--The pt wrapper styling enables the table header to stick with the project search bar-->
<DataTable
:value="projectsWithKnnMatches"
:rows="numberOfRows"
:rows-per-page-options="[10, 20, 30, 40, 50]"
:pt="{ wrapper: { style: { overflow: 'none' } } }"
:key="projectTableKey"
data-key="id"
paginator
@page="getProjectAssets"
>
<Column
v-for="(col, index) in selectedColumns"
:field="col.field"
:header="col.header"
:sortable="!['stats', 'score'].includes(col.field)"
:key="index"
:style="`width: ${getColumnWidth(col.field)}%`"
>
<template #body="{ data }">
<template v-if="col.field === 'score'">
{{ Math.round((data.metadata?.score ?? 0) * 100) + '%' }}
</template>
<template v-if="col.field === 'name'">
<a class="project-title-link" @click.stop="emit('open-project', data.id)">
{{ data.name }}
</a>
<a @click.stop="emit('open-project', data.id)">{{ data.name }}</a>
<ul>
<li
v-for="asset in data.projectAssets.slice(0, data.showMore ? data.projectAssets.length : 3)"
class="flex align-center gap-2"
:key="asset.id"
>
<tera-asset-icon :assetType="asset.assetType" />
<span v-html="highlight(asset.assetName, searchQuery)" />
</li>
</ul>
<Button
v-if="data.projectAssets.length > 3"
class="p-2 mt-2"
:label="data.showMore ? 'Show less' : 'Show more'"
text
size="small"
@click="data.showMore = !data.showMore"
/>
</template>
<template v-else-if="col.field === 'description'">
<tera-show-more-text :text="data.description" :lines="1" />
<p v-if="data.snippet" class="mt-2" v-html="data.snippet" />
</template>
<tera-show-more-text v-else-if="col.field === 'description'" :text="data.description" :lines="1" />
<template v-if="col.field === 'userName'">
{{ data.userName ?? '--' }}
</template>
Expand Down Expand Up @@ -68,21 +96,42 @@
</template>

<script setup lang="ts">
import { isEmpty } from 'lodash';
import { ref, watch } from 'vue';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import TeraShowMoreText from '@/components/widgets/tera-show-more-text.vue';
import { formatDdMmmYyyy } from '@/utils/date';
import DatasetIcon from '@/assets/svg/icons/dataset.svg?component';
import { Project } from '@/types/Types';
import { AssetType, Project } from '@/types/Types';
import type { PageState } from 'primevue/paginator';
import * as ProjectService from '@/services/project';
import { highlight } from '@/utils/text';
import Button from 'primevue/button';
import { v4 as uuidv4 } from 'uuid';
import TeraProjectMenu from './tera-project-menu.vue';
import TeraAssetIcon from '../widgets/tera-asset-icon.vue';
interface ProjectWithKnnSnippet extends Project {
snippet?: string;
showMore?: boolean;
}
defineProps<{
const props = defineProps<{
projects: Project[];
selectedColumns: { field: string; header: string }[];
searchQuery: string;
}>();
const emit = defineEmits(['open-project']);
const projectsWithKnnMatches = ref<ProjectWithKnnSnippet[]>([]);
const numberOfRows = ref(20);
const projectTableKey = ref(uuidv4());
let pageState: PageState = { page: 0, rows: numberOfRows.value, first: 0 };
let prevSearchQuery = '';
function formatStat(data, key) {
const stat = data?.[key];
return key === 'contributor-count' ? parseInt(stat ?? '1', 10) : parseInt(stat ?? '0', 10);
Expand All @@ -92,18 +141,46 @@ function formatStatTooltip(stat, displayName) {
return `${stat} ${displayName}${stat === 1 ? '' : 's'}`;
}
function getColumnWidth(columnField: string) {
switch (columnField) {
case 'description':
return 40;
case 'name':
return 40;
case 'score':
return 5;
default:
return 100;
}
async function getProjectAssets(event: PageState = pageState) {
pageState = event; // Save the current page state so we still know it when the watch is triggered
if (isEmpty(props.searchQuery)) return;
const { rows, first } = event;
const searchQuery = props.searchQuery.toLowerCase().trim();
// Just fetch the asset data we are seeing in the current page
projectsWithKnnMatches.value.slice(first, first + rows).forEach(async (project) => {
// If assets were fetched before from when we were on that page don't redo it
if (!isEmpty(project.projectAssets) && prevSearchQuery === searchQuery) return;
const projectWithAssets = (await ProjectService.get(project.id)) as ProjectWithKnnSnippet | null;
if (!projectWithAssets) return;
project.projectAssets = projectWithAssets.projectAssets.filter(
({ assetName, assetType }) =>
assetName.toLowerCase().includes(searchQuery) &&
// These assets dont have names
assetType !== AssetType.Simulation &&
assetType !== AssetType.NotebookSession
);
project.showMore = false;
project.snippet = project.description?.toLowerCase().includes(searchQuery)
? highlight(project.description, searchQuery)
: undefined;
});
prevSearchQuery = searchQuery;
}
watch(
() => props.projects,
() => {
projectTableKey.value = uuidv4();
projectsWithKnnMatches.value = props.projects;
getProjectAssets();
},
{ immediate: true }
);
</script>

<style scoped>
Expand All @@ -123,8 +200,50 @@ function getColumnWidth(columnField: string) {
}
.p-datatable {
border: 1px solid var(--surface-border-light);
border-radius: var(--border-radius);
/* Now the table header sticks along with the project search bar */
&:deep(thead) {
top: 105px;
z-index: 1;
}
}
:deep(.p-paginator-bottom) {
position: sticky;
bottom: 0;
outline: 1px solid var(--surface-border-light);
}
:deep(.p-paginator) {
border-radius: 0;
padding: var(--gap-2) var(--gap-4);
}
.p-datatable:deep(ul) {
margin-top: var(--gap-4);
color: var(--text-color-primary);
display: flex;
flex-direction: column;
gap: var(--gap-2);
font-size: var(--font-caption);
}
.p-datatable:deep(li > span) {
text-overflow: ellipsis;
display: block;
overflow: hidden;
max-width: 20vw;
}
.p-datatable:deep(p) {
color: var(--text-color-primary);
max-width: 22vw;
text-overflow: ellipsis;
display: block;
overflow: hidden;
}
.p-datatable:deep(.highlight) {
font-weight: var(--font-weight-semibold);
}
.p-datatable:deep(.p-datatable-tbody > tr > td),
Expand All @@ -136,7 +255,9 @@ function getColumnWidth(columnField: string) {
}
.p-datatable:deep(.p-datatable-thead > tr > th) {
padding: 1rem 0.5rem;
padding-left: var(--gap-5);
padding-top: var(--gap-3);
padding-bottom: var(--gap-3);
background-color: var(--surface-ground);
}
Expand All @@ -145,8 +266,8 @@ function getColumnWidth(columnField: string) {
}
.p-datatable:deep(.p-datatable-tbody > tr > td) {
padding-left: var(--gap-5);
color: var(--text-color-secondary);
padding: 0.5rem;
max-width: 32rem;
}
Expand All @@ -165,7 +286,10 @@ function getColumnWidth(columnField: string) {
.p-datatable:deep(.p-datatable-tbody > tr > td > a) {
color: var(--text-color-primary);
font-weight: var(--font-weight-semibold);
cursor: pointer;
text-overflow: ellipsis;
display: block;
overflow: hidden;
max-width: 20vw;
}
.p-datatable:deep(.p-datatable-tbody > tr > td > a:hover) {
Expand Down
Loading

0 comments on commit 3762825

Please sign in to comment.