Skip to content

Commit

Permalink
Merge branch 'main' into netbox
Browse files Browse the repository at this point in the history
  • Loading branch information
talboren authored Feb 3, 2025
2 parents 54ec21d + b6d3d95 commit 05102b8
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 160 deletions.
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ services:
image: us-central1-docker.pkg.dev/keephq/keep/keep-api
environment:
- AUTH_TYPE=NO_AUTH
- PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus
- KEEP_METRICS=true
volumes:
- ./state:/state

Expand Down
161 changes: 105 additions & 56 deletions keep-ui/features/filter/facets-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
import React, { useEffect, useMemo, useState } from "react";
import { Facet } from "./facet";
import { CreateFacetDto, FacetDto, FacetOptionDto, FacetOptionsQueries } from "./models";
import {
CreateFacetDto,
FacetDto,
FacetOptionDto,
FacetOptionsQueries,
} from "./models";
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useLocalStorage } from "@/utils/hooks/useLocalStorage";
import { AddFacetModal } from "./add-facet-modal";
import 'react-loading-skeleton/dist/skeleton.css';
import "react-loading-skeleton/dist/skeleton.css";
import clsx from "clsx";

/**
* It's facets state. Key is the facet id, and value is Set<string> of unselected options.
* If facet option value is selected, the set will contain it's display value, otherwise it will not.
*/
type FacetState = {
[facetId: string]: Set<string>;
}
};

function buildCel(facets: FacetDto[], facetOptions: { [key: string]: FacetOptionDto[] }, facetsState: FacetState): string {
function buildCel(
facets: FacetDto[],
facetOptions: { [key: string]: FacetOptionDto[] },
facetsState: FacetState
): string {
const cel = Object.values(facets)
.filter((facet) => facet.id in facetsState)
.map((facet) => {
const notSelectedOptions = Object.values(facetOptions[facet.id])
.filter((facetOption) => facetsState[facet.id]?.has(facetOption.display_name))
.map((option) => {
if (typeof option.value === 'string') {
return `'${option.value}'`;
} else if (option.value == null) {
return 'null';
}

return option.value;
});

if (!notSelectedOptions.length) {
return;
.filter((facet) => facet.id in facetsState)
.map((facet) => {
const notSelectedOptions = Object.values(facetOptions[facet.id])
.filter((facetOption) =>
facetsState[facet.id]?.has(facetOption.display_name)
)
.map((option) => {
if (typeof option.value === "string") {
return `'${option.value}'`;
} else if (option.value == null) {
return "null";
}

return `!(${facet.property_path} in [${notSelectedOptions.join(", ")}])`;
})
.filter((query) => query)
.map((facetCel) => `${facetCel}`)
.map((query) => query)
.join(" && ");
return option.value;
});

if (!notSelectedOptions.length) {
return;
}

return `!(${facet.property_path} in [${notSelectedOptions.join(", ")}])`;
})
.filter((query) => query)
.map((facetCel) => `${facetCel}`)
.map((query) => query)
.join(" && ");

return cel;
}
Expand All @@ -52,13 +64,19 @@ export interface FacetsPanelProps {
areFacetOptionsLoading?: boolean;
/** Token to clear filters related to facets */
clearFiltersToken?: string | null;
/**
/**
* Object with facets that should be unchecked by default.
* Key is the facet name, value is the list of option values to uncheck.
**/
uncheckedByDefaultOptionValues?: { [key: string]: string[] };
renderFacetOptionLabel?: (facetName: string, optionDisplayName: string) => JSX.Element | string | undefined;
renderFacetOptionIcon?: (facetName: string, optionDisplayName: string) => JSX.Element | undefined;
renderFacetOptionLabel?: (
facetName: string,
optionDisplayName: string
) => JSX.Element | string | undefined;
renderFacetOptionIcon?: (
facetName: string,
optionDisplayName: string
) => JSX.Element | undefined;
onCelChange: (cel: string) => void;
onAddFacet: (createFacet: CreateFacetDto) => void;
onDeleteFacet: (facetId: string) => void;
Expand Down Expand Up @@ -93,12 +111,18 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
const [celState, setCelState] = useState("");

function getFacetState(facetId: string): Set<string> {
if (!defaultStateHandledForFacetIds.has(facetId) && uncheckedByDefaultOptionValues && Object.keys(uncheckedByDefaultOptionValues).length) {
if (
!defaultStateHandledForFacetIds.has(facetId) &&
uncheckedByDefaultOptionValues &&
Object.keys(uncheckedByDefaultOptionValues).length
) {
const facetState = new Set<string>(...(facetsState[facetId] || []));
const facet = facets.find((f) => f.id === facetId);

if (facet) {
uncheckedByDefaultOptionValues[facet?.name]?.forEach((optionValue) => facetState.add(optionValue));
uncheckedByDefaultOptionValues[facet?.name]?.forEach((optionValue) =>
facetState.add(optionValue)
);
defaultStateHandledForFacetIds.add(facetId);
}

Expand All @@ -110,7 +134,7 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({

const isOptionSelected = (facet_id: string, option_id: string) => {
return !facetsState[facet_id] || !facetsState[facet_id].has(option_id);
}
};

function calculateFacetsState(newFacetsState: FacetState): void {
setFacetsState(newFacetsState);
Expand All @@ -125,20 +149,24 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
facets.forEach((facet) => {
const otherFacets = facets.filter((f) => f.id !== facet.id);

facetOptionQueries[facet.id] = buildCel(otherFacets, facetOptions, newFacetsState);
})
facetOptionQueries[facet.id] = buildCel(
otherFacets,
facetOptions,
newFacetsState
);
});

onReloadFacetOptions && onReloadFacetOptions(facetOptionQueries)
onReloadFacetOptions && onReloadFacetOptions(facetOptionQueries);
}

function toggleFacetOption(facetId: string, value: string) {
setClickedFacetId(facetId);
const facetState = getFacetState(facetId);

if (isOptionSelected(facetId, value)) {
facetState.add(value)
facetState.add(value);
} else {
facetState.delete(value)
facetState.delete(value);
}

calculateFacetsState({ ...facetsState, [facetId]: facetState });
Expand All @@ -148,14 +176,14 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
setClickedFacetId(facetId);
const facetState = getFacetState(facetId);

facetOptions[facetId].forEach(facetOption => {
facetOptions[facetId].forEach((facetOption) => {
if (facetOption.display_name === optionValue) {
facetState.delete(optionValue);
return;
}

facetState.add(facetOption.display_name);
})
});

calculateFacetsState({
...facetsState,
Expand All @@ -167,8 +195,9 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
setClickedFacetId(facetId);
const facetState = getFacetState(facetId);

Object.values(facetOptions[facetId])
.forEach((option) => (facetState.delete(option.display_name)));
Object.values(facetOptions[facetId]).forEach((option) =>
facetState.delete(option.display_name)
);

calculateFacetsState({
...facetsState,
Expand All @@ -180,15 +209,21 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
calculateFacetsState({});
}

useEffect(function clearFiltersWhenTokenChange(): void {
if (clearFiltersToken) {
clearFilters();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [clearFiltersToken]);
useEffect(
function clearFiltersWhenTokenChange(): void {
if (clearFiltersToken) {
clearFilters();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[clearFiltersToken]
);

return (
<section id={`${panelId}-facets`} className={"min-w-56 max-w-56 " + className}>
<section
id={`${panelId}-facets`}
className={clsx("min-w-48 max-w-48", className)}
>
<div className="space-y-2">
<div className="flex justify-between">
{/* Facet button */}
Expand All @@ -197,33 +232,45 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
className="p-1 pr-2 text-sm text-gray-600 hover:bg-gray-100 rounded flex items-center gap-1"
>
<PlusIcon className="h-4 w-4" />
Add Facet
Add facet
</button>
<button
onClick={() => clearFilters()}
className="p-1 pr-2 text-sm text-gray-600 hover:bg-gray-100 rounded flex items-center gap-1"
>
<XMarkIcon className="h-4 w-4" />
Clear filters
Reset
</button>
</div>

{facets?.map((facet, index) => (
<Facet
key={facet.id + index}
name={facet.name}
isStatic={facet.is_static}
options={facetOptions?.[facet.id]}
optionsLoading={!facetOptions?.[facet.id]}
optionsReloading={areFacetOptionsLoading && !!facet.id && clickedFacetId !== facet.id}
optionsReloading={
areFacetOptionsLoading &&
!!facet.id &&
clickedFacetId !== facet.id
}
onSelect={(value) => toggleFacetOption(facet.id, value)}
onSelectOneOption={(value) => selectOneFacetOption(facet.id, value)}
onSelectAllOptions={() => selectAllFacetOptions(facet.id)}
facetState={getFacetState(facet.id)}
facetKey={facet.id}
renderOptionLabel={(optionDisplayName) => renderFacetOptionLabel && renderFacetOptionLabel(facet.name, optionDisplayName)}
renderIcon={(optionDisplayName) => renderFacetOptionIcon && renderFacetOptionIcon(facet.name, optionDisplayName)}
onLoadOptions={() => onLoadFacetOptions && onLoadFacetOptions(facet.id)}
renderOptionLabel={(optionDisplayName) =>
renderFacetOptionLabel &&
renderFacetOptionLabel(facet.name, optionDisplayName)
}
renderIcon={(optionDisplayName) =>
renderFacetOptionIcon &&
renderFacetOptionIcon(facet.name, optionDisplayName)
}
onLoadOptions={() =>
onLoadFacetOptions && onLoadFacetOptions(facet.id)
}
onDelete={() => onDeleteFacet && onDeleteFacet(facet.id)}
/>
))}
Expand All @@ -232,7 +279,9 @@ export const FacetsPanel: React.FC<FacetsPanelProps> = ({
<AddFacetModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onAddFacet={(createFacet) => onAddFacet ? onAddFacet(createFacet) : null}
onAddFacet={(createFacet) =>
onAddFacet ? onAddFacet(createFacet) : null
}
/>
</div>
</section>
Expand Down
Loading

0 comments on commit 05102b8

Please sign in to comment.