Skip to content
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
12 changes: 10 additions & 2 deletions assets/src/components/cd/clusters/ClustersCharts.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Button, CaretUpIcon } from '@pluralsh/design-system'
import { SimpleAccordion } from 'components/ai/chatbot/multithread/MultiThreadViewerMessage'
import { POLL_INTERVAL } from 'components/cd/ContinuousDeployment'
import { useLogin } from 'components/contexts'
import { useProjectId } from 'components/contexts/ProjectsContext'
import { ClusterHealthScoresHeatmap } from 'components/home/clusteroverview/ClusterHealthScoresHeatmap'
import {
ClusterHealthScoresHeatmap,
normalizeHealthmapClusterCount,
} from 'components/home/clusteroverview/ClusterHealthScoresHeatmap'
import {
ClusterUpgradesChart,
UpgradeChartFilter,
Expand Down Expand Up @@ -56,10 +60,14 @@ export function ClustersCharts({
}

function ClusterChartsContent() {
const { configuration } = useLogin()
const projectId = useProjectId()
const healthmapClusterCount = normalizeHealthmapClusterCount(
configuration?.healthmapClusterCount
)

const { data: healthScoresData } = useClusterHealthScoresQuery({
variables: { projectId },
variables: { projectId, first: healthmapClusterCount },
fetchPolicy: 'cache-and-network',
pollInterval: POLL_INTERVAL,
})
Expand Down
12 changes: 10 additions & 2 deletions assets/src/components/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import {
aggregateHealthScoreStats,
ClusterHealthScoresFilterBtns,
ClusterHealthScoresHeatmap,
HealthScoreFilterLabel,
healthScoreLabelToRange,
HealthScoreFilterLabel,
normalizeHealthmapClusterCount,
} from './clusteroverview/ClusterHealthScoresHeatmap.tsx'
import {
aggregateUpgradeStats,
Expand All @@ -52,6 +53,7 @@ export function Home() {

function HomeClusters() {
const { borders } = useTheme()
const { configuration } = useLogin()
const projectId = useProjectId()
useSetBreadcrumbs(breadcrumbs)
// we don't want a double popup, and cloud setup would come first if relevant
Expand All @@ -75,9 +77,15 @@ function HomeClusters() {

const [selectedCluster, setSelectedCluster] =
useState<Nullable<ClustersRowFragment>>(null)
const healthmapClusterCount = normalizeHealthmapClusterCount(
configuration?.healthmapClusterCount
)

const { data: healthScoresData } = useClusterHealthScoresQuery({
variables: { projectId },
variables: {
projectId,
first: healthmapClusterCount,
},
fetchPolicy: 'cache-and-network',
pollInterval: POLL_INTERVAL,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import { ChartLegendItem, HomeFilterOptionCard } from '../HomeFilterOptionCard'
import { EmptyHeatmapSvg } from './ClusterHealthScoresHeatmapEmpty'

export type HealthScoreFilterLabel = keyof typeof healthScoreLabelToRange
export const DEFAULT_HEALTHMAP_CLUSTER_COUNT = 1000

export const normalizeHealthmapClusterCount = (
count: Nullable<number>
): number => (count && count > 0 ? count : DEFAULT_HEALTHMAP_CLUSTER_COUNT)

export function ClusterHealthScoresHeatmap({
clusters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { GqlError } from 'components/utils/Alert'

import { LoginContext } from 'components/contexts'

import { Permissions, hasRbac } from '../misc'

import { useThrottle } from 'components/hooks/useThrottle'
import { mapExistingNodes } from 'utils/graphql'
import { ListWrapperSC } from '../users/UsersList'
Expand Down Expand Up @@ -39,7 +37,7 @@ export function GroupsList({
const groups = useMemo(() => mapExistingNodes(data?.groups), [data?.groups])

const meta: GroupsListMeta = {
editable: !!me?.roles?.admin || hasRbac(me, Permissions.USERS),
editable: !!me?.roles?.admin,
setGroupEdit,
}

Expand Down
7 changes: 0 additions & 7 deletions assets/src/components/settings/usermanagement/misc.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ import {
import { useContext, useState } from 'react'

import { removeConnection, updateCache } from '../../../../utils/graphql'
import { hasRbac, Permissions } from '../misc'

import { EditPersonaAttributes } from './PersonaAttributesEdit'
import { EditPersonaBindings } from './PersonaBindingsEdit'
import PersonaView from './PersonaView'

export default function PersonaActions({ persona }: { persona: PersonaT }) {
const { me } = useContext<any>(LoginContext)
const editable = !!me.roles?.admin || hasRbac(me, Permissions.USERS)
const editable = !!me.roles?.admin

const [dialogKey, setDialogKey] = useState<
'confirmDelete' | 'editAttrs' | 'editBindings' | 'viewPersona' | ''
Expand Down
3 changes: 1 addition & 2 deletions assets/src/components/settings/usermanagement/users/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useTheme } from 'styled-components'
import { Confirm } from 'components/utils/Confirm'

import UserInfo from '../../../utils/UserInfo'
import { Permissions, hasRbac } from '../misc'

import { useUpdateUserMutation } from '../../../../generated/graphql.ts'

Expand All @@ -16,7 +15,7 @@ export function User({ user }: any) {
const [mutation, { loading, error }] = useUpdateUserMutation({
onCompleted: () => setConfirm(false),
})
const editable = !!me?.roles?.admin || hasRbac(me as any, Permissions.USERS)
const editable = !!me?.roles?.admin
const isAdmin = !!user.roles?.admin
const setAdmin = useCallback(
() =>
Expand Down
62 changes: 55 additions & 7 deletions assets/src/components/workbenches/tools/WorkbenchToolForm.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {
Button,
Checkbox,
Chip,
Divider,
Flex,
FormField,
Input2,
ListBoxItem,
Select,
SelectButton,
} from '@pluralsh/design-system'
import { useUpdateState } from 'components/hooks/useUpdateState'
import { FormBindings } from 'components/utils/bindings'
import {
PolicyBindingFragment,
Provider,
WorkbenchToolCategory,
WorkbenchToolAttributes,
WorkbenchToolConfigurationAttributes,
WorkbenchToolFragment,
Expand Down Expand Up @@ -165,9 +170,13 @@ export function WorkbenchToolForm({
const [deleteOpen, setDeleteOpen] = useState(false)
const [currentStep, setCurrentStep] =
useState<WorkbenchToolFormStep>('configuration')
const defaultCategories =
type === WorkbenchToolType.Mcp
? [WorkbenchToolCategory.Integration]
: TOOL_TYPE_TO_CATEGORIES[type]
const { state, update, hasUpdates } = useUpdateState<WorkbenchToolFormState>({
name: tool?.name ?? '',
categories: tool?.categories ?? TOOL_TYPE_TO_CATEGORIES[type],
categories: tool?.categories ?? defaultCategories,
configuration: sanitizeInitialConfiguration(tool),
cloudConnectionId: tool?.cloudConnection?.id,
mcpServerId: tool?.mcpServer?.id,
Expand All @@ -176,6 +185,7 @@ export function WorkbenchToolForm({
writeBindings: tool?.writeBindings?.filter(isNonNullable) ?? [],
})
const categories = TOOL_TYPE_TO_CATEGORIES[type] ?? []
const selectedCategories = (state.categories ?? []).filter(isNonNullable)
const hasRegisteredScm = Boolean(state.scmConnectionId)
const scmType = scmTypeForWorkbenchTool(type)
const configurationStepComplete =
Expand Down Expand Up @@ -270,10 +280,48 @@ export function WorkbenchToolForm({
onChange={(id) => update({ cloudConnectionId: id })}
/>
) : type === WorkbenchToolType.Mcp ? (
<McpServerSelectField
selectedId={state.mcpServerId ?? null}
onChange={(id) => update({ mcpServerId: id })}
/>
<>
<McpServerSelectField
selectedId={state.mcpServerId ?? null}
onChange={(id) => update({ mcpServerId: id })}
/>
<FormField label="Subagent Selection">
<Select
label="Subagent Selection"
selectionMode="multiple"
selectedKeys={new Set(selectedCategories)}
onSelectionChange={(keys) => {
const next = Array.from(keys).map(
(key) => key as WorkbenchToolCategory
)
if (next.length > 0) update({ categories: next })
}}
triggerButton={
<SelectButton>
<Flex
align="center"
gap="xsmall"
>
<Chip
fillLevel={3}
size="small"
>
{selectedCategories.length}
</Chip>
<span>Subagents selected</span>
</Flex>
</SelectButton>
}
>
{categories.map((category) => (
<ListBoxItem
key={category}
label={categoryToLabel[category]}
/>
))}
</Select>
</FormField>
</>
) : (
<>
{scmType ? (
Expand All @@ -291,14 +339,14 @@ export function WorkbenchToolForm({
/>
</>
)}
{categories.length > 1 && (
{type !== WorkbenchToolType.Mcp && categories.length > 1 && (
<FormField label="Allowed capabilities (must select at least one)">
<Flex
direction="column"
gap="xsmall"
>
{categories.map((category) => {
const selected = (state.categories ?? []).filter(Boolean)
const selected = selectedCategories
const isChecked = selected.includes(category)
const canUncheck = selected.length > 1
return (
Expand Down
11 changes: 10 additions & 1 deletion assets/src/components/workbenches/tools/workbenchToolsUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,13 @@ export const TOOL_TYPE_TO_CATEGORIES: Record<
[WorkbenchToolType.BitbucketDatacenter]: [WorkbenchToolCategory.Scm],
[WorkbenchToolType.AzureDevops]: [WorkbenchToolCategory.Scm],
[WorkbenchToolType.Http]: [WorkbenchToolCategory.Integration],
[WorkbenchToolType.Mcp]: [WorkbenchToolCategory.Integration],
[WorkbenchToolType.Mcp]: [
WorkbenchToolCategory.Integration,
WorkbenchToolCategory.Observability,
WorkbenchToolCategory.Infrastructure,
WorkbenchToolCategory.Coding,
WorkbenchToolCategory.Verification,
],
[WorkbenchToolType.Sentry]: [WorkbenchToolCategory.ErrorTracking],
[WorkbenchToolType.Splunk]: [WorkbenchToolCategory.Logs],
[WorkbenchToolType.Dynatrace]: [
Expand Down Expand Up @@ -293,6 +299,9 @@ export const categoryToLabel: Record<WorkbenchToolCategory, string> = {
[WorkbenchToolCategory.ErrorTracking]: 'Error tracking',
[WorkbenchToolCategory.Infrastructure]: 'Infrastructure',
[WorkbenchToolCategory.Function]: 'Cloud Function',
[WorkbenchToolCategory.Coding]: 'Coding',
[WorkbenchToolCategory.Verification]: 'Verification',
[WorkbenchToolCategory.Observability]: 'Observability',
}

export type WorkbenchToolCard = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function WorkbenchJobActivity({
const { spacing } = useTheme()
const { id, status, type, prompt, agentRun, result } = activity
const isRunning = isJobRunning(status)
const isRejected = status === WorkbenchJobActivityStatus.Rejected

if (type === WorkbenchJobActivityType.Conclusion)
return (
Expand Down Expand Up @@ -169,7 +170,7 @@ export function WorkbenchJobActivity({
tooltip="Go to agent run details"
/>
)}
{status == WorkbenchJobActivityStatus.Failed && (
{(status === WorkbenchJobActivityStatus.Failed || isRejected) && (
<FailedFilledIcon
size={12}
color="icon-danger"
Expand Down Expand Up @@ -238,6 +239,7 @@ export function WorkbenchJobMemoGroup({
key={activity.id}
activity={activity}
textStream={textStreamMap[activity.id] ?? ''}
showMemoPrefix={activities.length > 1}
/>
))}
</Flex>
Expand Down Expand Up @@ -286,30 +288,40 @@ export function WorkbenchJobMemoGroup({
function WorkbenchJobMemo({
activity,
textStream,
showMemoPrefix = true,
}: {
activity: WorkbenchJobActivityFragment
textStream: string
showMemoPrefix?: boolean
}) {
const { prompt, result, status } = activity
const [isOpen, setIsOpen] = useState(false)
const [finishedAnimating, setFinishedAnimating] = useState(false)
const isRunning = isJobRunning(status)
const isFailed = status === WorkbenchJobActivityStatus.Failed
const isRejected = status === WorkbenchJobActivityStatus.Rejected
const content = textStream || result?.output || prompt || ''
const label =
content ||
result?.error ||
(isRejected ? 'Rejected workbench notes update' : null) ||
(isFailed ? 'Failed to update workbench notes' : 'Updated workbench notes')

return (
<MemoRowSC>
<ClickableLabelSC onClick={() => setIsOpen(true)}>
<MemoLabelSC $shimmer={isRunning}>
Memo <SpanSC $color="text-light">{label}</SpanSC>
{showMemoPrefix ? (
<>
Memo <SpanSC $color="text-light">{label}</SpanSC>
</>
) : (
label
)}
</MemoLabelSC>
</ClickableLabelSC>
{result?.jobUpdate && <MemoActivityIcon jobUpdate={result.jobUpdate} />}
{isFailed && (
{(isFailed || isRejected) && (
<FailedFilledIcon
size={12}
color="icon-danger"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export const isActivityTerminal = (
status: Nullable<WorkbenchJobActivityStatus>
) =>
status === WorkbenchJobActivityStatus.Successful ||
status === WorkbenchJobActivityStatus.Failed
status === WorkbenchJobActivityStatus.Failed ||
status === WorkbenchJobActivityStatus.Rejected

const lastActivityId = (
activities: WorkbenchJobActivityFragment[]
Expand Down
Loading
Loading