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
11 changes: 11 additions & 0 deletions .cursor/rules/ecto-queries-in-schema.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
description: Keep Ecto queries inside schema modules
globs: "**/*.{ex,exs}"
alwaysApply: false
---

# Ecto Queries

There should never be queries outside of schema modules (`lib/console/schema`).

Define `from`/`where`/`join` queries on the relevant `Console.Schema.*` module. Resolvers and contexts only compose those functions and call `Repo.all/1`.
11 changes: 11 additions & 0 deletions .cursor/rules/graphql-dataloaders.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
description: Use Absinthe dataloaders for batched GraphQL fields
globs: lib/console/graphql/**/*.ex
alwaysApply: false
---

# GraphQL Dataloaders

Use Absinthe dataloaders for batched per-parent fields. Do not use `Absinthe.Resolution.Helpers.batch/3`.

Add a `Dataloader.KV` source in `lib/console/graphql/resolvers/dataloader.ex`, register it in `Console.GraphQl`, and resolve with `manual_dataloader` or the loader's `resolve` helper.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
3. Never use a nested case when a with expression is possible.
4. Defer to ecto for input validation. You should rarely need to use put_change, trust the builtins.
5. Avoid usage of `if` and `cond` if a more elegant case expression is possible.
6. There should never be queries outside of schema modules.
7. Use Absinthe dataloaders for batched GraphQL fields, not `batch/3`.

## Broad go guidance

Expand Down
196 changes: 137 additions & 59 deletions go/client/models_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 110 additions & 0 deletions js/console/src/components/flows/FlowActionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { GitPullIcon, ListBoxItem, PeopleIcon } from '@pluralsh/design-system'
import {
PermissionsIdType,
PermissionsModal,
} from 'components/cd/utils/PermissionsModal'
import { useLogin } from 'components/contexts'
import { FlowFavoriteStar } from 'components/flows/FlowFavoriteButton'
import { flowTabPath } from 'components/flows/flowHealth'
import { MoreMenu } from 'components/utils/MoreMenu'
import { hasAccess } from 'components/utils/persona'
import { FlowBasicWithBindingsFragment } from 'generated/graphql'
import { MouseEvent, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'

function stopBubble(event: { stopPropagation: () => void }) {
event.stopPropagation()
}

function stopLink(event: MouseEvent) {
event.preventDefault()
event.stopPropagation()
}

export function FlowActionsMenu({
flow,
search,
refetch,
favorited,
onToggleFavorite,
}: {
flow: FlowBasicWithBindingsFragment
search: string
refetch: () => void
favorited: boolean
onToggleFavorite: () => void
}) {
const { personaConfiguration } = useLogin()
const navigate = useNavigate()
const showPermissionsBtn = hasAccess(
personaConfiguration,
'flows.permissions'
)
const showPipelines = hasAccess(personaConfiguration, 'flows.pipelines')
const [menuKey, setMenuKey] = useState('')
const favoriteLabel = favorited
? 'Unfavorite this flow'
: 'Favorite this flow'
const onSelect = {
pipelines: () => navigate(flowTabPath(flow.name, 'pipelines', search)),
favorite: onToggleFavorite,
}

return (
<ActionsSC
onClick={stopLink}
onPointerDown={stopBubble}
onMouseDown={stopBubble}
>
<MoreMenu
onSelectionChange={(key: string) => {
const action = onSelect[key as keyof typeof onSelect]

if (action) action()
else setMenuKey(key)
}}
>
{showPermissionsBtn && (
<ListBoxItem
key="permissions"
label="Update permission"
leftContent={<PeopleIcon />}
textValue="Update permission"
/>
)}
{showPipelines && (
<ListBoxItem
key="pipelines"
label="View pipelines"
leftContent={<GitPullIcon />}
textValue="View pipelines"
/>
)}
<ListBoxItem
key="favorite"
label={favoriteLabel}
leftContent={<FlowFavoriteStar />}
textValue={favoriteLabel}
/>
</MoreMenu>
{showPermissionsBtn && (
<PermissionsModal
id={flow.id}
type={PermissionsIdType.Flow}
bindings={flow}
header="Flow permissions"
refetch={refetch}
open={menuKey === 'permissions'}
onClose={() => setMenuKey('')}
/>
)}
</ActionsSC>
)
}

const ActionsSC = styled.div({
position: 'relative',
zIndex: 1,
pointerEvents: 'auto',
})
Loading
Loading