From 1d8c6197aca5d0adf354e1fa888f980d28afeb2d Mon Sep 17 00:00:00 2001 From: Nail Badiullin Date: Thu, 11 May 2023 12:38:39 +0400 Subject: [PATCH 1/2] FE: Expose cluster ACL list (#3662) Co-authored-by: iliax Co-authored-by: Ilya Kuramshin Co-authored-by: Roman Zabaluev --- .../src/components/ACLPage/ACLPage.tsx | 13 ++ .../components/ACLPage/List/List.styled.ts | 44 +++++ .../src/components/ACLPage/List/List.tsx | 153 ++++++++++++++++++ .../ACLPage/List/__test__/List.spec.tsx | 74 +++++++++ .../components/ClusterPage/ClusterPage.tsx | 11 ++ .../src/components/Nav/ClusterMenu.tsx | 5 + .../components/common/Button/Button.styled.ts | 2 +- .../ConfirmationModal/ConfirmationModal.tsx | 2 +- .../components/common/Icons/DeleteIcon.tsx | 5 +- .../common/MultiSelect/MultiSelect.styled.ts | 12 +- .../src/components/common/NewTable/Table.tsx | 36 ++++- .../components/contexts/ConfirmContext.tsx | 5 + kafka-ui-react-app/src/lib/api.ts | 2 + kafka-ui-react-app/src/lib/fixtures/acls.ts | 37 +++++ kafka-ui-react-app/src/lib/hooks/api/acl.ts | 67 ++++++++ .../src/lib/hooks/useConfirm.ts | 3 +- kafka-ui-react-app/src/lib/paths.ts | 7 + kafka-ui-react-app/src/theme/theme.ts | 86 +++++++++- 18 files changed, 550 insertions(+), 14 deletions(-) create mode 100644 kafka-ui-react-app/src/components/ACLPage/ACLPage.tsx create mode 100644 kafka-ui-react-app/src/components/ACLPage/List/List.styled.ts create mode 100644 kafka-ui-react-app/src/components/ACLPage/List/List.tsx create mode 100644 kafka-ui-react-app/src/components/ACLPage/List/__test__/List.spec.tsx create mode 100644 kafka-ui-react-app/src/lib/fixtures/acls.ts create mode 100644 kafka-ui-react-app/src/lib/hooks/api/acl.ts diff --git a/kafka-ui-react-app/src/components/ACLPage/ACLPage.tsx b/kafka-ui-react-app/src/components/ACLPage/ACLPage.tsx new file mode 100644 index 0000000000..616198716d --- /dev/null +++ b/kafka-ui-react-app/src/components/ACLPage/ACLPage.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Routes, Route } from 'react-router-dom'; +import ACList from 'components/ACLPage/List/List'; + +const ACLPage = () => { + return ( + + } /> + + ); +}; + +export default ACLPage; diff --git a/kafka-ui-react-app/src/components/ACLPage/List/List.styled.ts b/kafka-ui-react-app/src/components/ACLPage/List/List.styled.ts new file mode 100644 index 0000000000..287214e1e6 --- /dev/null +++ b/kafka-ui-react-app/src/components/ACLPage/List/List.styled.ts @@ -0,0 +1,44 @@ +import styled from 'styled-components'; + +export const EnumCell = styled.div` + text-transform: capitalize; +`; + +export const DeleteCell = styled.div` + svg { + cursor: pointer; + } +`; + +export const Chip = styled.div<{ + chipType?: 'default' | 'success' | 'danger' | 'secondary' | string; +}>` + width: fit-content; + text-transform: capitalize; + padding: 2px 8px; + font-size: 12px; + line-height: 16px; + border-radius: 16px; + color: ${({ theme }) => theme.tag.color}; + background-color: ${({ theme, chipType }) => { + switch (chipType) { + case 'success': + return theme.tag.backgroundColor.green; + case 'danger': + return theme.tag.backgroundColor.red; + case 'secondary': + return theme.tag.backgroundColor.secondary; + default: + return theme.tag.backgroundColor.gray; + } + }}; +`; + +export const PatternCell = styled.div` + display: flex; + align-items: center; + + ${Chip} { + margin-left: 4px; + } +`; diff --git a/kafka-ui-react-app/src/components/ACLPage/List/List.tsx b/kafka-ui-react-app/src/components/ACLPage/List/List.tsx new file mode 100644 index 0000000000..499f255c30 --- /dev/null +++ b/kafka-ui-react-app/src/components/ACLPage/List/List.tsx @@ -0,0 +1,153 @@ +import React from 'react'; +import { ColumnDef } from '@tanstack/react-table'; +import { useTheme } from 'styled-components'; +import PageHeading from 'components/common/PageHeading/PageHeading'; +import Table from 'components/common/NewTable'; +import DeleteIcon from 'components/common/Icons/DeleteIcon'; +import { useConfirm } from 'lib/hooks/useConfirm'; +import useAppParams from 'lib/hooks/useAppParams'; +import { useAcls, useDeleteAcl } from 'lib/hooks/api/acl'; +import { ClusterName } from 'redux/interfaces'; +import { + KafkaAcl, + KafkaAclNamePatternType, + KafkaAclPermissionEnum, +} from 'generated-sources'; + +import * as S from './List.styled'; + +const ACList: React.FC = () => { + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const theme = useTheme(); + const { data: aclList } = useAcls(clusterName); + const { deleteResource } = useDeleteAcl(clusterName); + const modal = useConfirm(true); + + const [rowId, setRowId] = React.useState(''); + + const onDeleteClick = (acl: KafkaAcl | null) => { + if (acl) { + modal('Are you sure want to delete this ACL record?', () => + deleteResource(acl) + ); + } + }; + + const columns = React.useMemo[]>( + () => [ + { + header: 'Principal', + accessorKey: 'principal', + size: 257, + }, + { + header: 'Resource', + accessorKey: 'resourceType', + // eslint-disable-next-line react/no-unstable-nested-components + cell: ({ getValue }) => ( + {getValue().toLowerCase()} + ), + size: 145, + }, + { + header: 'Pattern', + accessorKey: 'resourceName', + // eslint-disable-next-line react/no-unstable-nested-components + cell: ({ getValue, row }) => { + let chipType; + if ( + row.original.namePatternType === KafkaAclNamePatternType.PREFIXED + ) { + chipType = 'default'; + } + + if ( + row.original.namePatternType === KafkaAclNamePatternType.LITERAL + ) { + chipType = 'secondary'; + } + return ( + + {getValue()} + {chipType ? ( + + {row.original.namePatternType.toLowerCase()} + + ) : null} + + ); + }, + size: 257, + }, + { + header: 'Host', + accessorKey: 'host', + size: 257, + }, + { + header: 'Operation', + accessorKey: 'operation', + // eslint-disable-next-line react/no-unstable-nested-components + cell: ({ getValue }) => ( + {getValue().toLowerCase()} + ), + size: 121, + }, + { + header: 'Permission', + accessorKey: 'permission', + // eslint-disable-next-line react/no-unstable-nested-components + cell: ({ getValue }) => ( + () === KafkaAclPermissionEnum.ALLOW + ? 'success' + : 'danger' + } + > + {getValue().toLowerCase()} + + ), + size: 111, + }, + { + id: 'delete', + // eslint-disable-next-line react/no-unstable-nested-components + cell: ({ row }) => { + return ( + onDeleteClick(row.original)}> + + + ); + }, + size: 76, + }, + ], + [rowId] + ); + + const onRowHover = (value: unknown) => { + if (value && typeof value === 'object' && 'id' in value) { + setRowId(value.id as string); + } + }; + + return ( + <> + + setRowId('')} + /> + + ); +}; + +export default ACList; diff --git a/kafka-ui-react-app/src/components/ACLPage/List/__test__/List.spec.tsx b/kafka-ui-react-app/src/components/ACLPage/List/__test__/List.spec.tsx new file mode 100644 index 0000000000..0c39681bbd --- /dev/null +++ b/kafka-ui-react-app/src/components/ACLPage/List/__test__/List.spec.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, WithRoute } from 'lib/testHelpers'; +import { screen } from '@testing-library/dom'; +import userEvent from '@testing-library/user-event'; +import { clusterACLPath } from 'lib/paths'; +import ACList from 'components/ACLPage/List/List'; +import { useAcls, useDeleteAcl } from 'lib/hooks/api/acl'; +import { aclPayload } from 'lib/fixtures/acls'; + +jest.mock('lib/hooks/api/acl', () => ({ + useAcls: jest.fn(), + useDeleteAcl: jest.fn(), +})); + +describe('ACLList Component', () => { + const clusterName = 'local'; + const renderComponent = () => + render( + + + , + { + initialEntries: [clusterACLPath(clusterName)], + } + ); + + describe('ACLList', () => { + describe('when the acls are loaded', () => { + beforeEach(() => { + (useAcls as jest.Mock).mockImplementation(() => ({ + data: aclPayload, + })); + (useDeleteAcl as jest.Mock).mockImplementation(() => ({ + deleteResource: jest.fn(), + })); + }); + + it('renders ACLList with records', async () => { + renderComponent(); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect(screen.getAllByRole('row').length).toEqual(4); + }); + + it('shows delete icon on hover', async () => { + const { container } = renderComponent(); + const [trElement] = screen.getAllByRole('row'); + await userEvent.hover(trElement); + const deleteElement = container.querySelector('svg'); + expect(deleteElement).not.toHaveStyle({ + fill: 'transparent', + }); + }); + }); + + describe('when it has no acls', () => { + beforeEach(() => { + (useAcls as jest.Mock).mockImplementation(() => ({ + data: [], + })); + (useDeleteAcl as jest.Mock).mockImplementation(() => ({ + deleteResource: jest.fn(), + })); + }); + + it('renders empty ACLList with message', async () => { + renderComponent(); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect( + screen.getByRole('row', { name: 'No ACL items found' }) + ).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/kafka-ui-react-app/src/components/ClusterPage/ClusterPage.tsx b/kafka-ui-react-app/src/components/ClusterPage/ClusterPage.tsx index c2f6a13fbe..29d2015f61 100644 --- a/kafka-ui-react-app/src/components/ClusterPage/ClusterPage.tsx +++ b/kafka-ui-react-app/src/components/ClusterPage/ClusterPage.tsx @@ -13,6 +13,7 @@ import { clusterTopicsRelativePath, clusterConfigRelativePath, getNonExactPath, + clusterAclRelativePath, } from 'lib/paths'; import ClusterContext from 'components/contexts/ClusterContext'; import PageLoader from 'components/common/PageLoader/PageLoader'; @@ -30,6 +31,7 @@ const ClusterConfigPage = React.lazy( const ConsumerGroups = React.lazy( () => import('components/ConsumerGroups/ConsumerGroups') ); +const AclPage = React.lazy(() => import('components/ACLPage/ACLPage')); const ClusterPage: React.FC = () => { const { clusterName } = useAppParams(); @@ -51,6 +53,9 @@ const ClusterPage: React.FC = () => { ClusterFeaturesEnum.TOPIC_DELETION ), hasKsqlDbConfigured: features.includes(ClusterFeaturesEnum.KSQL_DB), + hasAclViewConfigured: + features.includes(ClusterFeaturesEnum.KAFKA_ACL_VIEW) || + features.includes(ClusterFeaturesEnum.KAFKA_ACL_EDIT), }; }, [clusterName, data]); @@ -95,6 +100,12 @@ const ClusterPage: React.FC = () => { element={} /> )} + {contextValue.hasAclViewConfigured && ( + } + /> + )} {appInfo.hasDynamicConfig && ( = ({ {hasFeatureConfigured(ClusterFeaturesEnum.KSQL_DB) && ( )} + {(hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_ACL_VIEW) || + hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_ACL_EDIT)) && ( + + )} )} diff --git a/kafka-ui-react-app/src/components/common/Button/Button.styled.ts b/kafka-ui-react-app/src/components/common/Button/Button.styled.ts index 76fe47ec71..a436d01e75 100644 --- a/kafka-ui-react-app/src/components/common/Button/Button.styled.ts +++ b/kafka-ui-react-app/src/components/common/Button/Button.styled.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; export interface ButtonProps { - buttonType: 'primary' | 'secondary'; + buttonType: 'primary' | 'secondary' | 'danger'; buttonSize: 'S' | 'M' | 'L'; isInverted?: boolean; } diff --git a/kafka-ui-react-app/src/components/common/ConfirmationModal/ConfirmationModal.tsx b/kafka-ui-react-app/src/components/common/ConfirmationModal/ConfirmationModal.tsx index 86833cc7ba..1b882c9462 100644 --- a/kafka-ui-react-app/src/components/common/ConfirmationModal/ConfirmationModal.tsx +++ b/kafka-ui-react-app/src/components/common/ConfirmationModal/ConfirmationModal.tsx @@ -26,7 +26,7 @@ const ConfirmationModal: React.FC = () => { Cancel ))} diff --git a/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx b/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx index f0958972ee..d68eda2547 100644 --- a/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx +++ b/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx @@ -6,6 +6,8 @@ interface ConfirmContextType { setContent: React.Dispatch>; setConfirm: React.Dispatch void) | undefined>>; cancel: () => void; + dangerButton: boolean; + setDangerButton: React.Dispatch>; } export const ConfirmContext = React.createContext( @@ -17,6 +19,7 @@ export const ConfirmContextProvider: React.FC< > = ({ children }) => { const [content, setContent] = useState(null); const [confirm, setConfirm] = useState<(() => void) | undefined>(undefined); + const [dangerButton, setDangerButton] = useState(false); const cancel = () => { setContent(null); @@ -31,6 +34,8 @@ export const ConfirmContextProvider: React.FC< confirm, setConfirm, cancel, + dangerButton, + setDangerButton, }} > {children} diff --git a/kafka-ui-react-app/src/lib/api.ts b/kafka-ui-react-app/src/lib/api.ts index deef9a7c65..19423d2ac3 100644 --- a/kafka-ui-react-app/src/lib/api.ts +++ b/kafka-ui-react-app/src/lib/api.ts @@ -10,6 +10,7 @@ import { ConsumerGroupsApi, AuthorizationApi, ApplicationConfigApi, + AclsApi, } from 'generated-sources'; import { BASE_PARAMS } from 'lib/constants'; @@ -25,3 +26,4 @@ export const kafkaConnectApiClient = new KafkaConnectApi(apiClientConf); export const consumerGroupsApiClient = new ConsumerGroupsApi(apiClientConf); export const authApiClient = new AuthorizationApi(apiClientConf); export const appConfigApiClient = new ApplicationConfigApi(apiClientConf); +export const aclApiClient = new AclsApi(apiClientConf); diff --git a/kafka-ui-react-app/src/lib/fixtures/acls.ts b/kafka-ui-react-app/src/lib/fixtures/acls.ts new file mode 100644 index 0000000000..3eecf7cea8 --- /dev/null +++ b/kafka-ui-react-app/src/lib/fixtures/acls.ts @@ -0,0 +1,37 @@ +import { + KafkaAcl, + KafkaAclResourceType, + KafkaAclNamePatternType, + KafkaAclPermissionEnum, + KafkaAclOperationEnum, +} from 'generated-sources'; + +export const aclPayload: KafkaAcl[] = [ + { + principal: 'User 1', + resourceName: 'Topic', + resourceType: KafkaAclResourceType.TOPIC, + host: '_host1', + namePatternType: KafkaAclNamePatternType.LITERAL, + permission: KafkaAclPermissionEnum.ALLOW, + operation: KafkaAclOperationEnum.READ, + }, + { + principal: 'User 2', + resourceName: 'Topic', + resourceType: KafkaAclResourceType.TOPIC, + host: '_host1', + namePatternType: KafkaAclNamePatternType.PREFIXED, + permission: KafkaAclPermissionEnum.ALLOW, + operation: KafkaAclOperationEnum.READ, + }, + { + principal: 'User 3', + resourceName: 'Topic', + resourceType: KafkaAclResourceType.TOPIC, + host: '_host1', + namePatternType: KafkaAclNamePatternType.LITERAL, + permission: KafkaAclPermissionEnum.DENY, + operation: KafkaAclOperationEnum.READ, + }, +]; diff --git a/kafka-ui-react-app/src/lib/hooks/api/acl.ts b/kafka-ui-react-app/src/lib/hooks/api/acl.ts new file mode 100644 index 0000000000..da6a463fff --- /dev/null +++ b/kafka-ui-react-app/src/lib/hooks/api/acl.ts @@ -0,0 +1,67 @@ +import { aclApiClient as api } from 'lib/api'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { ClusterName } from 'redux/interfaces'; +import { showSuccessAlert } from 'lib/errorHandling'; +import { KafkaAcl } from 'generated-sources'; + +export function useAcls(clusterName: ClusterName) { + return useQuery( + ['clusters', clusterName, 'acls'], + () => api.listAcls({ clusterName }), + { + suspense: false, + } + ); +} + +export function useCreateAclMutation(clusterName: ClusterName) { + return useMutation( + (data: KafkaAcl) => + api.createAcl({ + clusterName, + kafkaAcl: data, + }), + { + onSuccess() { + showSuccessAlert({ + message: 'Your ACL was created successfully', + }); + }, + } + ); +} + +export function useCreateAcl(clusterName: ClusterName) { + const mutate = useCreateAclMutation(clusterName); + + return { + createResource: async (param: KafkaAcl) => { + return mutate.mutateAsync(param); + }, + ...mutate, + }; +} + +export function useDeleteAclMutation(clusterName: ClusterName) { + const queryClient = useQueryClient(); + return useMutation( + (acl: KafkaAcl) => api.deleteAcl({ clusterName, kafkaAcl: acl }), + { + onSuccess: () => { + showSuccessAlert({ message: 'ACL deleted' }); + queryClient.invalidateQueries(['clusters', clusterName, 'acls']); + }, + } + ); +} + +export function useDeleteAcl(clusterName: ClusterName) { + const mutate = useDeleteAclMutation(clusterName); + + return { + deleteResource: async (param: KafkaAcl) => { + return mutate.mutateAsync(param); + }, + ...mutate, + }; +} diff --git a/kafka-ui-react-app/src/lib/hooks/useConfirm.ts b/kafka-ui-react-app/src/lib/hooks/useConfirm.ts index 1387f7666b..baac856c59 100644 --- a/kafka-ui-react-app/src/lib/hooks/useConfirm.ts +++ b/kafka-ui-react-app/src/lib/hooks/useConfirm.ts @@ -1,12 +1,13 @@ import { ConfirmContext } from 'components/contexts/ConfirmContext'; import React, { useContext } from 'react'; -export const useConfirm = () => { +export const useConfirm = (danger = false) => { const context = useContext(ConfirmContext); return ( message: React.ReactNode, callback: () => void | Promise ) => { + context?.setDangerButton(danger); context?.setContent(message); context?.setConfirm(() => async () => { await callback(); diff --git a/kafka-ui-react-app/src/lib/paths.ts b/kafka-ui-react-app/src/lib/paths.ts index 6571f1684c..9cee7ca285 100644 --- a/kafka-ui-react-app/src/lib/paths.ts +++ b/kafka-ui-react-app/src/lib/paths.ts @@ -285,3 +285,10 @@ export const clusterConfigPath = ( const clusterNewConfigRelativePath = 'create-new-cluster'; export const clusterNewConfigPath = `/ui/clusters/${clusterNewConfigRelativePath}`; + +// ACL +export const clusterAclRelativePath = 'acl'; +export const clusterAclNewRelativePath = 'create-new-acl'; +export const clusterACLPath = ( + clusterName: ClusterName = RouteParams.clusterName +) => `${clusterPath(clusterName)}/${clusterAclRelativePath}`; diff --git a/kafka-ui-react-app/src/theme/theme.ts b/kafka-ui-react-app/src/theme/theme.ts index e7ee0047e6..f42eb8c6d5 100644 --- a/kafka-ui-react-app/src/theme/theme.ts +++ b/kafka-ui-react-app/src/theme/theme.ts @@ -31,6 +31,7 @@ const Colors = { '15': '#C2F0D1', '30': '#85E0A3', '40': '#5CD685', + '50': '#33CC66', '60': '#29A352', }, brand: { @@ -231,6 +232,7 @@ const baseTheme = { white: Colors.neutral[10], red: Colors.red[10], blue: Colors.blue[10], + secondary: Colors.neutral[15], }, color: Colors.neutral[90], }, @@ -416,8 +418,8 @@ export const theme = { disabled: Colors.red[20], }, color: { - normal: Colors.neutral[90], - disabled: Colors.neutral[30], + normal: Colors.neutral[0], + disabled: Colors.neutral[0], }, invertedColors: { normal: Colors.brand[50], @@ -695,6 +697,44 @@ export const theme = { textColor: Colors.brand[50], deleteIconColor: Colors.brand[50], }, + acl: { + table: { + deleteIcon: Colors.neutral[50], + }, + create: { + radioButtons: { + green: { + normal: { + background: Colors.neutral[0], + text: Colors.neutral[50], + }, + active: { + background: Colors.green[50], + text: Colors.neutral[0], + }, + hover: { + background: Colors.green[10], + text: Colors.neutral[90], + }, + }, + gray: { + normal: { + background: Colors.neutral[0], + text: Colors.neutral[50], + }, + active: { + background: Colors.neutral[10], + text: Colors.neutral[90], + }, + hover: { + background: Colors.neutral[5], + text: Colors.neutral[90], + }, + }, + red: {}, + }, + }, + }, }; export type ThemeType = typeof theme; @@ -818,8 +858,8 @@ export const darkTheme: ThemeType = { disabled: Colors.red[20], }, color: { - normal: Colors.neutral[90], - disabled: Colors.neutral[30], + normal: Colors.neutral[0], + disabled: Colors.neutral[0], }, invertedColors: { normal: Colors.brand[50], @@ -1155,4 +1195,42 @@ export const darkTheme: ThemeType = { color: Colors.neutral[0], }, }, + acl: { + table: { + deleteIcon: Colors.neutral[50], + }, + create: { + radioButtons: { + green: { + normal: { + background: Colors.neutral[0], + text: Colors.neutral[50], + }, + active: { + background: Colors.green[50], + text: Colors.neutral[0], + }, + hover: { + background: Colors.green[10], + text: Colors.neutral[0], + }, + }, + gray: { + normal: { + background: Colors.neutral[0], + text: Colors.neutral[50], + }, + active: { + background: Colors.neutral[10], + text: Colors.neutral[90], + }, + hover: { + background: Colors.neutral[5], + text: Colors.neutral[90], + }, + }, + red: {}, + }, + }, + }, }; From e7429ce6c6e93497ec4c5c8ae530236e5207f630 Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Thu, 11 May 2023 17:13:09 +0800 Subject: [PATCH 2/2] Update release drafter configs --- .github/release_drafter.yaml | 8 ++++++++ .github/workflows/release_drafter.yml | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/release_drafter.yaml b/.github/release_drafter.yaml index c9cbe51a07..3679535540 100644 --- a/.github/release_drafter.yaml +++ b/.github/release_drafter.yaml @@ -16,18 +16,26 @@ exclude-labels: - 'type/refactoring' categories: + - title: '🚩 Breaking Changes' + labels: + - 'impact/changelog' + - title: '⚙️Features' labels: - 'type/feature' + - title: '🪛Enhancements' labels: - 'type/enhancement' + - title: '🔨Bug Fixes' labels: - 'type/bug' + - title: 'Security' labels: - 'type/security' + - title: '⎈ Helm/K8S Changes' labels: - 'scope/k8s' diff --git a/.github/workflows/release_drafter.yml b/.github/workflows/release_drafter.yml index 742254b942..e9516cb663 100644 --- a/.github/workflows/release_drafter.yml +++ b/.github/workflows/release_drafter.yml @@ -2,18 +2,33 @@ name: Release Drafter on: push: - # branches to consider in the event; optional, defaults to all branches: - master workflow_dispatch: + inputs: + version: + description: 'Release version' + required: false + branch: + description: 'Target branch' + required: false + default: 'master' + +permissions: + contents: read jobs: update_release_draft: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - uses: release-drafter/release-drafter@v5 with: config-name: release_drafter.yaml disable-autolabeler: true + version: ${{ github.event.inputs.version }} + commitish: ${{ github.event.inputs.branch }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ {flexRender(columnDef.cell, getContext())}