Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Filters] Add Edge Types list #2445

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions src/components/App/SideBar/FilterSearch/Edges/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { useState } from 'react'
import styled from 'styled-components'
import { Flex } from '~/components/common/Flex'
import PlusIcon from '~/components/Icons/PlusIcon'
import { colors } from '~/utils'
import { PopoverBody } from '..'
import { SchemaLink } from '~/network/fetchSourcesData'

type Props = {
handleEdgeClick: (type: string) => void
selectedEdges: string[]
links: SchemaLink[]
}

export const Edges = ({ handleEdgeClick, selectedEdges, links }: Props) => {
const [showAllEdges, setShowAllEdges] = useState(false)

const schemasPerRow = 3
const MAX_ROWS = 4
const maxVisibleSchemas = schemasPerRow * MAX_ROWS

return (
<>
<PopoverHeader>
<div>Edges</div>
<CountSelectedWrapper>
<Count>{selectedEdges.length}</Count>
<SelectedText>Selected</SelectedText>
</CountSelectedWrapper>
</PopoverHeader>
<PopoverBody>
<SchemaTypeWrapper>
{links.map((link) => (
<SchemaType
key={link?.edge_type}
isSelected={selectedEdges?.includes(link?.edge_type as string)}
onClick={() => handleEdgeClick(link?.edge_type as string)}
>
{link?.edge_type}
</SchemaType>
))}
</SchemaTypeWrapper>
{!showAllEdges && links.length > maxVisibleSchemas && (
<ViewMoreButton onClick={() => setShowAllEdges(true)}>
<PlusIconWrapper>
<PlusIcon /> View More
</PlusIconWrapper>
</ViewMoreButton>
)}
</PopoverBody>
</>
)
}

const PopoverHeader = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 8px;
font-family: Barlow;
font-size: 18px;
font-weight: 500;
`

const CountSelectedWrapper = styled.div`
font-size: 13px;
display: flex;
align-items: center;
`

const Count = styled.span`
color: ${colors.white};
`

const SelectedText = styled.span`
color: ${colors.GRAY3};
margin-left: 4px;
`

const PlusIconWrapper = styled.span`
display: flex;
justify-content: space-between;
align-items: center;
gap: 6px;

svg {
width: 23px;
height: 23px;
fill: none;
margin-top: 2px;
}
`

const SchemaTypeWrapper = styled(Flex).attrs({
align: 'center',
direction: 'row',
grow: 1,
justify: 'flex-start',
})`
flex-wrap: wrap;
gap: 10px;
padding-right: 10px;
margin-right: calc(0px - 16px);
`

const SchemaType = styled(Flex).attrs({
align: 'center',
direction: 'row',
justify: 'flex-start',
})<{ isSelected: boolean }>`
color: ${({ isSelected }) => (isSelected ? colors.black : colors.white)};
background: ${({ isSelected }) => (isSelected ? colors.white : colors.BUTTON1_PRESS)};
padding: 6px 10px 6px 8px;
font-family: Barlow;
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 15px;
letter-spacing: 0.78px;
margin: 0 3px;
border-radius: 200px;
cursor: pointer;

&:hover {
background: ${({ isSelected }) => (isSelected ? colors.white : colors.BUTTON1_PRESS)};
}

&:active {
background: ${colors.white};
color: ${colors.black};
}
`

const ViewMoreButton = styled.button`
background: transparent;
color: ${colors.white};
border: none;
padding: 6px 12px 6px 3px;
margin-top: 20px;
cursor: pointer;
border-radius: 4px;
font-family: Barlow;
font-size: 13px;
font-weight: 500;

&:hover {
background: ${colors.BUTTON1_HOVER};
}

&:active {
background: ${colors.BUTTON1_PRESS};
}
`
22 changes: 12 additions & 10 deletions src/components/App/SideBar/FilterSearch/__tests__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,33 @@ const mockFetchData = jest.fn()
const mockSetAbortRequests = jest.fn()
const mockOnClose = jest.fn()

jest.mock('~/stores/useSchemaStore', () => ({
useSchemaStore: jest.fn(),
}))

describe('FilterSearch Component', () => {
const mockSetSchemas = jest.fn()
const mockSetSchemaLinks = jest.fn()
const mockSchemaAll = [{ type: 'Type1' }, { type: 'Type2' }, { type: 'Type3' }]
const mockEdges = [{ edge_type: 'Edge1' }, { edge_type: 'Edge2' }, { edge_type: 'Edge3' }]

beforeEach(() => {
jest.clearAllMocks()

//
// Mock useDataStore
;(useDataStore as jest.Mock).mockReturnValue({
setFilters: mockSetFilters,
fetchData: mockFetchData,
setAbortRequests: mockSetAbortRequests,
})

//
;(useSchemaStore as jest.Mock).mockReturnValue([mockSchemaAll, mockSetSchemas]) // Return an array
// Mock useSchemaStore to return an array for destructuring
;(useSchemaStore as jest.Mock).mockReturnValue([mockSchemaAll, mockSetSchemas, mockEdges, mockSetSchemaLinks])

//
// Mock useFeatureFlagStore
;(useFeatureFlagStore as jest.Mock).mockReturnValue({ fastFiltersFeatureFlag: true })

//
;(getSchemaAll as jest.Mock).mockResolvedValue({ schemas: mockSchemaAll })
// Mock getSchemaAll
;(getSchemaAll as jest.Mock).mockResolvedValue({
schemas: mockSchemaAll,
edges: mockEdges,
})
})

const renderComponent = () =>
Expand Down Expand Up @@ -105,6 +106,7 @@ describe('FilterSearch Component', () => {
await waitFor(() => {
expect(mockSetFilters).toHaveBeenCalledWith({
node_type: ['Type1'],
edge_type: [],
limit: 30,
depth: '1',
top_node_count: '10',
Expand Down
40 changes: 37 additions & 3 deletions src/components/App/SideBar/FilterSearch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'
import styled from 'styled-components'
import ClearIcon from '~/components/Icons/ClearIcon'
import { Flex } from '~/components/common/Flex'
import { getSchemaAll } from '~/network/fetchSourcesData'
import { getSchemaAll, SchemaLink } from '~/network/fetchSourcesData'
import { useDataStore } from '~/stores/useDataStore'
import { useFeatureFlagStore } from '~/stores/useFeatureFlagStore'
import { useSchemaStore } from '~/stores/useSchemaStore'
Expand All @@ -13,6 +13,7 @@ import { Hops } from './Hops'
import { MaxResults } from './MaxResults'
import { NodeTypes } from './NodeTypes'
import { SourceNodes } from './SourceNodes'
import { Edges } from './Edges'

type Props = {
anchorEl: HTMLElement | null
Expand All @@ -22,15 +23,23 @@ type Props = {

const defaultValues = {
selectedTypes: [] as string[],
selectedEdges: [] as string[],
hops: 1,
sourceNodes: 10,
maxResults: 30,
}

export const FilterSearch = ({ anchorEl, setAnchorEl, onClose }: Props) => {
const [schemaAll, setSchemaAll] = useSchemaStore((s) => [s.schemas, s.setSchemas])
const [schemaAll, setSchemaAll, links, setSchemaLinks] = useSchemaStore((s) => [
s.schemas,
s.setSchemas,
s.links,
s.setSchemaLinks,
])

const { abortFetchData, resetGraph, setFilters } = useDataStore((s) => s)
const [selectedTypes, setSelectedTypes] = useState<string[]>(defaultValues.selectedTypes)
const [selectedEdges, setSelectedEdges] = useState<string[]>(defaultValues.selectedEdges)
const [hops, setHops] = useState(defaultValues.hops)
const [sourceNodes, setSourceNodes] = useState<number>(defaultValues.sourceNodes)
const [maxResults, setMaxResults] = useState<number>(defaultValues.maxResults)
Expand All @@ -41,27 +50,49 @@ export const FilterSearch = ({ anchorEl, setAnchorEl, onClose }: Props) => {
try {
const response = await getSchemaAll()

const uniqueEdges = Array.from(
response.edges
.filter((edge) => edge.edge_type !== 'CHILD_OF')
.reduce((map, edge) => {
if (!map.has(edge.edge_type)) {
map.set(edge.edge_type, edge)
}

return map
}, new Map<string, SchemaLink>())
.values(),
)

setSchemaLinks(uniqueEdges)

setSchemaAll(response.schemas.filter((schema) => !schema.is_deleted))
} catch (error) {
console.error('Error fetching schema:', error)
}
}

fetchSchemaData()
}, [setSchemaAll])
}, [setSchemaAll, setSchemaLinks])

const handleSchemaTypeClick = (type: string) => {
setSelectedTypes((prevSelectedTypes) =>
prevSelectedTypes.includes(type) ? prevSelectedTypes.filter((t) => t !== type) : [...prevSelectedTypes, type],
)
}

const handleEdgeClick = (edge: string) => {
setSelectedEdges((prevSelectedEdges) =>
prevSelectedEdges.includes(edge) ? prevSelectedEdges.filter((t) => t !== edge) : [...prevSelectedEdges, edge],
)
}

const handleSchemaTypesSelect = (types: string[]) => {
setSelectedTypes(types)
}

const resetToDefaultValues = () => {
setSelectedTypes(defaultValues.selectedTypes)
setSelectedEdges(defaultValues.selectedEdges)
setHops(defaultValues.hops)
setSourceNodes(defaultValues.sourceNodes)
setMaxResults(defaultValues.maxResults)
Expand All @@ -76,6 +107,7 @@ export const FilterSearch = ({ anchorEl, setAnchorEl, onClose }: Props) => {
const handleFiltersApply = async () => {
setFilters({
node_type: selectedTypes,
edge_type: selectedEdges,
limit: maxResults,
depth: hops.toString(),
top_node_count: sourceNodes.toString(),
Expand Down Expand Up @@ -109,6 +141,8 @@ export const FilterSearch = ({ anchorEl, setAnchorEl, onClose }: Props) => {

<NodeTypes handleSchemaTypeClick={handleSchemaTypeClick} schemaAll={schemaAll} selectedTypes={selectedTypes} />
<LineBar />
<Edges handleEdgeClick={handleEdgeClick} links={links} selectedEdges={selectedEdges} />
<LineBar />
<SourceNodes setSourceNodes={setSourceNodes} sourceNodes={sourceNodes} />
<LineBar />
<Hops hops={hops} setHops={setHops} />
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type FilterParams = {
top_node_count: string
include_properties: string
node_type: string[]
edge_type: string[]
search_method: string
free?: string
word?: string // Add other optional filter properties as needed
Expand Down
Loading