Skip to content
Merged
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
174 changes: 90 additions & 84 deletions .github/workflows/release-workflow-schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,87 +88,93 @@ jobs:
name: workflow-schema
path: workflow-yaml-json-schema.json

release-schema:
runs-on: ubuntu-latest
needs: generate-schema
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}

steps:
- name: Download schema artifact
uses: actions/download-artifact@v4
with:
name: workflow-schema
path: .
- name: Checkout schema repository
uses: actions/checkout@v4
with:
repository: ${{ env.SCHEMA_REPO_NAME }}
token: ${{ secrets.SCHEMA_REPO_PAT }}
path: schema-repo

- name: Set target branch variable
id: set_branch
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT
else
echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT
fi

- name: Create or switch to target branch in schema repo
working-directory: schema-repo
run: |
git fetch origin
if git show-ref --verify --quiet refs/heads/${{ steps.set_branch.outputs.branch }}; then
git checkout ${{ steps.set_branch.outputs.branch }}
else
git checkout -b ${{ steps.set_branch.outputs.branch }}
fi

- name: Copy schema to target repository
run: |
cp workflow-yaml-json-schema.json schema-repo/schema.json

# Update schema with version info
jq --arg version "${{ needs.generate-schema.outputs.version }}" \
--arg id "https://raw.githubusercontent.com/${{ env.SCHEMA_REPO_NAME }}/v${{ needs.generate-schema.outputs.version }}/schema.json" \
'. + {version: $version, "$id": $id}' \
schema-repo/schema.json > schema-repo/schema.tmp.json

mv schema-repo/schema.tmp.json schema-repo/schema.json

- name: Check if schema changed
id: check_changes
working-directory: schema-repo
run: |
git add schema.json
if git diff --cached --quiet schema.json; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi

- name: Commit and push schema
if: steps.check_changes.outputs.changed == 'true'
working-directory: schema-repo
run: |
git config user.name "Keep Schema Bot"
git config user.email "[email protected]"
git commit -m "Release schema v${{ needs.generate-schema.outputs.version }}"
git push origin ${{ steps.set_branch.outputs.branch }}
if [ "${{ steps.set_branch.outputs.branch }}" = "main" ]; then
git tag "v${{ needs.generate-schema.outputs.version }}"
git push origin "v${{ needs.generate-schema.outputs.version }}"
fi

- name: Create GitHub Release
if: steps.check_changes.outputs.changed == 'true' && steps.set_branch.outputs.branch == 'main'
uses: softprops/action-gh-release@v1
with:
repository: ${{ env.SCHEMA_REPO_NAME }}
tag_name: v${{ needs.generate-schema.outputs.version }}
name: Release v${{ needs.generate-schema.outputs.version }}
body: |
Automated release of schema version v${{ needs.generate-schema.outputs.version }}.
env:
GITHUB_TOKEN: ${{ secrets.SCHEMA_REPO_PAT }}
# NOTE: The `release-schema` job below is temporarily disabled. It pushes the
# generated schema to the external `keephq/keep-workflow-schema` repo using
# `secrets.SCHEMA_REPO_PAT`, which is currently failing with "Bad credentials"
# (expired/invalid PAT). Schema generation/validation (the `generate-schema`
# job above) still runs. Re-enable once the SCHEMA_REPO_PAT secret is refreshed.
#
# release-schema:
# runs-on: ubuntu-latest
# needs: generate-schema
# if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
#
# steps:
# - name: Download schema artifact
# uses: actions/download-artifact@v4
# with:
# name: workflow-schema
# path: .
# - name: Checkout schema repository
# uses: actions/checkout@v4
# with:
# repository: ${{ env.SCHEMA_REPO_NAME }}
# token: ${{ secrets.SCHEMA_REPO_PAT }}
# path: schema-repo
#
# - name: Set target branch variable
# id: set_branch
# run: |
# if [ "${{ github.event_name }}" = "pull_request" ]; then
# echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT
# else
# echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT
# fi
#
# - name: Create or switch to target branch in schema repo
# working-directory: schema-repo
# run: |
# git fetch origin
# if git show-ref --verify --quiet refs/heads/${{ steps.set_branch.outputs.branch }}; then
# git checkout ${{ steps.set_branch.outputs.branch }}
# else
# git checkout -b ${{ steps.set_branch.outputs.branch }}
# fi
#
# - name: Copy schema to target repository
# run: |
# cp workflow-yaml-json-schema.json schema-repo/schema.json
#
# # Update schema with version info
# jq --arg version "${{ needs.generate-schema.outputs.version }}" \
# --arg id "https://raw.githubusercontent.com/${{ env.SCHEMA_REPO_NAME }}/v${{ needs.generate-schema.outputs.version }}/schema.json" \
# '. + {version: $version, "$id": $id}' \
# schema-repo/schema.json > schema-repo/schema.tmp.json
#
# mv schema-repo/schema.tmp.json schema-repo/schema.json
#
# - name: Check if schema changed
# id: check_changes
# working-directory: schema-repo
# run: |
# git add schema.json
# if git diff --cached --quiet schema.json; then
# echo "changed=false" >> $GITHUB_OUTPUT
# else
# echo "changed=true" >> $GITHUB_OUTPUT
# fi
#
# - name: Commit and push schema
# if: steps.check_changes.outputs.changed == 'true'
# working-directory: schema-repo
# run: |
# git config user.name "Keep Schema Bot"
# git config user.email "[email protected]"
# git commit -m "Release schema v${{ needs.generate-schema.outputs.version }}"
# git push origin ${{ steps.set_branch.outputs.branch }}
# if [ "${{ steps.set_branch.outputs.branch }}" = "main" ]; then
# git tag "v${{ needs.generate-schema.outputs.version }}"
# git push origin "v${{ needs.generate-schema.outputs.version }}"
# fi
#
# - name: Create GitHub Release
# if: steps.check_changes.outputs.changed == 'true' && steps.set_branch.outputs.branch == 'main'
# uses: softprops/action-gh-release@v1
# with:
# repository: ${{ env.SCHEMA_REPO_NAME }}
# tag_name: v${{ needs.generate-schema.outputs.version }}
# name: Release v${{ needs.generate-schema.outputs.version }}
# body: |
# Automated release of schema version v${{ needs.generate-schema.outputs.version }}.
# env:
# GITHUB_TOKEN: ${{ secrets.SCHEMA_REPO_PAT }}
3 changes: 2 additions & 1 deletion docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@
"deployment/provision/overview",
"deployment/provision/provider",
"deployment/provision/workflow",
"deployment/provision/dashboard"
"deployment/provision/dashboard",
"deployment/provision/mapping"
]
},
"deployment/secret-store",
Expand Down
7 changes: 5 additions & 2 deletions docs/snippets/providers/anthropic-snippet-autogenerated.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py
{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py
Do not edit it manually, as it will be overwritten */}

## Authentication
This provider requires authentication.
- **api_key**: Anthropic API Key (required: True, sensitive: True)
- **model**: Claude model to use (required: False, sensitive: False)
- **system_prompt**: System prompt that sets Claude's role for all requests in this provider. (required: False, sensitive: False)


## In workflows
Expand All @@ -19,8 +21,9 @@ steps:
config: "{{ provider.my_provider_name }}"
with:
prompt: {value} # The prompt to query the model with.
model: {value} # The model to query.
model: {value} # The model to query (overrides provider config).
max_tokens: {value} # The maximum number of tokens to generate.
system_prompt: {value} # System prompt override for this call.
structured_output_format: {value} # The structured output format to use.
```

Expand Down
66 changes: 48 additions & 18 deletions keep-ui/features/filter/facet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { FacetValue } from "./facet-value";
import { FacetDto, FacetOptionDto } from "./models";
import { TrashIcon } from "@heroicons/react/24/outline";
import { useExistingFacetsPanelStore } from "./store";
import { stringToValue, valueToString } from "./store/utils";
import { isLazyFacet, stringToValue, valueToString } from "./store/utils";

export interface FacetProps {
facet: FacetDto;
Expand All @@ -30,8 +30,13 @@ export const Facet: React.FC<FacetProps> = ({
// Get preset name from URL
const presetName = pathname?.split("/").pop() || "default";

// Store open/close state in localStorage with a unique key per preset and facet
const [isOpen, setIsOpen] = useState<boolean>(true);
// Lazy facets (high-cardinality user-defined facets) start collapsed and only
// fetch their options when the user expands them. Static facets (severity,
// status, source, ...) always render eagerly. Eagerly mounting and loading
// options for every lazy facet is what froze the alerts page when many (200+)
// facets existed (see issue #6577).
const isLazy = isLazyFacet(facet);
const [isOpen, setIsOpen] = useState<boolean>(!isLazy);
const [isLoaded, setIsLoaded] = useState<boolean>(!!options?.length);
const [isLoading, setIsLoading] = useState<boolean>(false);

Expand All @@ -40,8 +45,14 @@ export const Facet: React.FC<FacetProps> = ({
const facetRef = useRef(facet);
facetRef.current = facet;

const facetOptionsLoadingState = useExistingFacetsPanelStore(
(state) => state.facetOptionsLoadingState
// Subscribe only to this facet's slice of the loading/selection state.
// Previously every Facet subscribed to the whole `facetOptionsLoadingState`
// and `facetsState` objects, so toggling one option re-rendered all facets.
const facetLoadingState = useExistingFacetsPanelStore(
(state) => state.facetOptionsLoadingState[facet.id]
);
const hasLoadingState = useExistingFacetsPanelStore(
(state) => Object.keys(state.facetOptionsLoadingState).length > 0
);
const toggleFacetOption = useExistingFacetsPanelStore(
(state) => state.toggleFacetOption
Expand All @@ -52,20 +63,35 @@ export const Facet: React.FC<FacetProps> = ({
const selectAllFacetOptions = useExistingFacetsPanelStore(
(state) => state.selectAllFacetOptions
);
const facetsState = useExistingFacetsPanelStore((state) => state.facetsState);
const facetState: Record<string, boolean> = useMemo(
() => facetsState?.[facet.id],
[facet.id, facetsState]
const setFacetActive = useExistingFacetsPanelStore(
(state) => state.setFacetActive
);
const facetState: Record<string, boolean> = useExistingFacetsPanelStore(
(state) => state.facetsState?.[facet.id]
);

const facetsConfig = useExistingFacetsPanelStore(
(state) => state.facetsConfig
const facetConfig = useExistingFacetsPanelStore(
(state) => state.facetsConfig?.[facet.id]
);

const isFacetActive = useExistingFacetsPanelStore(
(state) => !!state.activeFacetIds?.[facet.id]
);
const facetConfig = facetsConfig?.[facet.id];

const facetStateRef = useRef(facetState);
facetStateRef.current = facetState;

// Auto-open a lazy facet once it becomes active — either it received a
// selection (e.g. restored from URL query params after mount) or it was just
// added by the user via "Add Facet" — so the user can see its values.
const didAutoOpenRef = useRef(false);
useEffect(() => {
if (isLazy && !didAutoOpenRef.current && (facetState || isFacetActive)) {
didAutoOpenRef.current = true;
setIsOpen(true);
}
}, [isLazy, facetState, isFacetActive]);

function getSelectedValues(): string[] {
return Object.keys(facetStateRef.current || {});
}
Expand Down Expand Up @@ -140,7 +166,14 @@ export const Facet: React.FC<FacetProps> = ({
};

const handleExpandCollapse = (isOpen: boolean) => {
setIsOpen(!isOpen);
const willOpen = !isOpen;
setIsOpen(willOpen);

// Mark the facet active when expanding so its options get loaded. Lazy
// facets are inactive until expanded (#6577).
if (willOpen) {
setFacetActive(facet.id);
}

if (!isLoaded && !isLoading) {
onLoadOptions && onLoadOptions();
Expand Down Expand Up @@ -205,10 +238,7 @@ export const Facet: React.FC<FacetProps> = ({
}

function renderBody() {
if (
facetOptionsLoadingState[facet.id] === "loading" ||
!Object.keys(facetOptionsLoadingState).length
) {
if (facetLoadingState === "loading" || !hasLoadingState) {
return Array.from({ length: 3 }).map((_, index) =>
renderSkeleton(`skeleton-${index}`)
);
Expand Down Expand Up @@ -283,7 +313,7 @@ export const Facet: React.FC<FacetProps> = ({
</div>
)}
<div
className={`max-h-60 overflow-y-auto${facetOptionsLoadingState[facet.id] === "reloading" ? " pointer-events-none opacity-70" : ""}`}
className={`max-h-60 overflow-y-auto${facetLoadingState === "reloading" ? " pointer-events-none opacity-70" : ""}`}
>
{renderBody()}
</div>
Expand Down
21 changes: 14 additions & 7 deletions keep-ui/features/filter/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,22 @@ export const useFacetOptions = (
}

const fetchedData: FacetOptionsDict = swrValue.data.response;
const newFacetOptions: FacetOptionsDict = JSON.parse(
JSON.stringify(mergedFacetOptions || {})
);
// Shallow copy of the per-facet map only; we never mutate the existing
// option arrays/objects, so a full JSON deep-clone (which duplicated the
// entire options tree on every poll and spiked memory with high facet
// cardinality, see #6577) is unnecessary.
const newFacetOptions: FacetOptionsDict = { ...(mergedFacetOptions || {}) };
Object.entries(fetchedData).forEach(([facetId, newOptions]) => {
if (newFacetOptions[facetId]) {
const currentFacetOptionsMap = newFacetOptions[facetId].reduce(
const existingOptions = newFacetOptions[facetId];
if (existingOptions) {
// Preserve previously known option values with a 0 match count so they
// remain visible/selectable, then overlay the freshly fetched counts.
const currentFacetOptionsMap = existingOptions.reduce(
(accumulator, oldOption) => {
accumulator[oldOption.display_name] = oldOption;
oldOption.matches_count = 0;
accumulator[oldOption.display_name] = {
...oldOption,
matches_count: 0,
};
return accumulator;
},
{} as Record<string, FacetOptionDto>
Expand Down
Loading
Loading