Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supported hidden (deleted) properties for rewards and proposals #5168

Merged
merged 8 commits into from
Feb 4, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
disableEditPropertyOption?: boolean;
boardType?: 'proposals' | 'rewards';
showCard?: (cardId: string | null) => void;
isTemplate?: boolean; // whether or not to always hide deleted properties
};

function CardDetailProperties(props: Props) {
Expand All @@ -52,7 +53,8 @@
pageUpdatedBy,
syncWithPageId,
mutator = defaultMutator,
disableEditPropertyOption
disableEditPropertyOption,
isTemplate
} = props;

const [newTemplateId, setNewTemplateId] = useState('');
Expand Down Expand Up @@ -196,14 +198,10 @@
id: 'CardDetailProperty.confirm-delete-heading',
defaultMessage: 'Confirm Delete Property'
}),
subText: intl.formatMessage(
{
id: 'CardDetailProperty.confirm-delete-subtext',
defaultMessage:
'Are you sure you want to delete the property "{propertyName}"? Deleting it will delete the property from all cards in this board.'
},
{ propertyName: propertyTemplate.name }
),
subText:
board.id === '__defaultBoard' // __defaultBoard type sis used to capture reward and proposal properties
? `Are you sure you want to delete the property "${propertyTemplate.name}"? Deleting it will not destroy data from previous submissions.`
: `Are you sure you want to delete the property "${propertyTemplate.name}"? Deleting it will delete the property from all cards in this board.`,
confirmButtonText: intl.formatMessage({
id: 'CardDetailProperty.delete-action-button',
defaultMessage: 'Delete'
Expand Down Expand Up @@ -234,6 +232,14 @@
setShowConfirmationDialog(true);
}

// only used for proposal and reward boards at this time. we fully delete the properties of other boards
function restoreProperty(propertyTemplate: IPropertyTemplate) {
mutator.updateProperty(board, propertyTemplate.id, {
...propertyTemplate,
deletedAt: undefined
});
}

function getDeleteDisabled(template: IPropertyTemplate) {
if (
views.some((view) => view.fields.viewType === 'calendar' && view.fields.dateDisplayPropertyId === template.id)
Expand Down Expand Up @@ -267,14 +273,16 @@
}}
/>
),
[mutator, props.boardType, board, activeView, isSmallScreen]

Check warning on line 276 in components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has missing dependencies: 'addPropertyPopupState', 'intl', and 'syncRelationProperty'. Either include them or remove the dependency array

Check warning on line 276 in components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx

View workflow job for this annotation

GitHub Actions / Test app

React Hook useMemo has missing dependencies: 'addPropertyPopupState', 'intl', and 'syncRelationProperty'. Either include them or remove the dependency array

Check warning on line 276 in components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has missing dependencies: 'addPropertyPopupState', 'intl', and 'syncRelationProperty'. Either include them or remove the dependency array

Check warning on line 276 in components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx

View workflow job for this annotation

GitHub Actions / Validate code

React Hook useMemo has missing dependencies: 'addPropertyPopupState', 'intl', and 'syncRelationProperty'. Either include them or remove the dependency array
);

let boardProperties = board.fields.cardProperties || [];
let boardProperties = (board.fields.cardProperties || [])
// hide deleted properties unless they were deleted before the card was created and it's not a template
.filter((p) => !p.deletedAt || (!isTemplate && new Date(p.deletedAt) > new Date(card.createdAt)));

if (board.fields.sourceType === 'proposals') {
// remove properties that belong to a different template
boardProperties = board.fields?.cardProperties.filter(
boardProperties = boardProperties.filter(
(property) => !property.templateId || card.fields.properties[property.id] !== undefined
);
}
Expand All @@ -299,6 +307,7 @@
showCard={props.showCard}
deleteDisabledMessage={getDeleteDisabled(propertyTemplate)}
onDelete={() => onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate)}
onRestore={() => restoreProperty(propertyTemplate)}
onTypeAndNameChanged={(newType: PropertyType, newName: string, relationData?: RelationPropertyData) => {
onPropertyChangeSetAndOpenConfirmationDialog(newType, newName, propertyTemplate, relationData);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function CardDetailProperty({
board,
card,
onDelete,
onRestore,
pageUpdatedBy,
pageUpdatedAt,
deleteDisabledMessage,
Expand All @@ -56,6 +57,7 @@ export function CardDetailProperty({
board: Board;
onTypeAndNameChanged: (newType: PropertyType, newName: string, relationData?: RelationPropertyData) => void;
onDelete: VoidFunction;
onRestore: VoidFunction;
pageUpdatedAt: string;
pageUpdatedBy: string;
deleteDisabledMessage?: string;
Expand All @@ -66,7 +68,12 @@ export function CardDetailProperty({
}) {
const [isDragging, isOver, columnRef] = useSortable('column', property, !readOnly, onDrop);
const changePropertyPopupState = usePopupState({ variant: 'popover', popupId: 'card-property' });
const propertyTooltip = property.name.length > 20 ? property.name : '';
const propertyTooltip = property.deletedAt
? 'This property was deleted'
: property.name.length > 20
? property.name
: '';

return (
<Stack
ref={columnRef}
Expand All @@ -83,7 +90,7 @@ export function CardDetailProperty({
className='octo-propertyrow'
>
{(readOnly || disableEditPropertyOption) && (
<PropertyLabel tooltip={propertyTooltip} readOnly>
<PropertyLabel tooltip={propertyTooltip} readOnly deleted={!!property.deletedAt}>
{property.name}
</PropertyLabel>
)}
Expand All @@ -101,14 +108,18 @@ export function CardDetailProperty({
<DragIndicatorIcon className='icons' fontSize='small' color='secondary' />
<Tooltip title={propertyTooltip} disableInteractive>
<span>
<Button>{property.name}</Button>
<Button deleted={!!property.deletedAt}>{property.name}</Button>
</span>
</Tooltip>
</PropertyNameContainer>
<Menu {...bindMenu(changePropertyPopupState)}>
<PropertyMenu
board={board}
onDelete={onDelete}
onRestore={() => {
onRestore();
changePropertyPopupState.close();
}}
deleteDisabled={deleteDisabledMessage?.length !== 0}
property={property}
onTypeAndNameChanged={(newType, newName, relationData) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type PropertyLabelProps = {
highlighted?: boolean;
fullWidth?: boolean;
tooltip?: string;
deleted?: boolean; // when showing deleted card properties
};

const Wrapper = styled(({ highlighted, fullWidth, ...props }: any) => <Box {...props} />)<{
Expand All @@ -31,7 +32,8 @@ export function PropertyLabel({
fullWidth,
tooltip,
readOnly = true,
highlighted
highlighted,
deleted
}: PropertyLabelProps) {
if (readOnly) {
return (
Expand All @@ -42,7 +44,7 @@ export function PropertyLabel({
>
<Tooltip title={tooltip} disableInteractive>
<span>
<Button rightIcon icon={required && <Asterisk>&nbsp;*</Asterisk>}>
<Button rightIcon icon={required && <Asterisk>&nbsp;*</Asterisk>} deleted={deleted}>
{children}
</Button>
</span>
Expand Down
2 changes: 2 additions & 0 deletions components/common/DatabaseEditor/widgets/buttons/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Props = {
rightIcon?: boolean;
disabled?: boolean;
'data-test'?: string;
deleted?: boolean;
};

function Button({ size = 'small', ...props }: Props): JSX.Element {
Expand All @@ -31,6 +32,7 @@ function Button({ size = 'small', ...props }: Props): JSX.Element {
title={props.title}
onBlur={props.onBlur}
disabled={props.disabled}
style={{ textDecoration: props.deleted ? 'line-through' : undefined }}
>
{!props.rightIcon && props.icon}
<span>{props.children}</span>
Expand Down
23 changes: 17 additions & 6 deletions components/common/DatabaseEditor/widgets/propertyMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import RestoreIcon from '@mui/icons-material/RestoreOutlined';
import {
ListItemIcon,
ListItemText,
Expand All @@ -25,6 +26,7 @@ import { typeDisplayName } from './typeDisplayName';
type Props = {
onTypeAndNameChanged: (newType: PropertyType, newName: string, relationData?: RelationPropertyData) => void;
onDelete: (id: string) => void;
onRestore: (id: string) => void;
deleteDisabled?: boolean;
property: IPropertyTemplate;
board: Board;
Expand Down Expand Up @@ -84,12 +86,21 @@ const PropertyMenu = React.memo((props: Props) => {
</Tooltip>
</MenuItem>
<Divider sx={{ my: '0 !important' }} />
<MenuItem onClick={() => props.onDelete(propertyId)}>
<ListItemIcon>
<DeleteOutlinedIcon fontSize='small' />
</ListItemIcon>
<ListItemText>Delete property</ListItemText>
</MenuItem>
{props.property.deletedAt ? (
<MenuItem onClick={() => props.onRestore(propertyId)}>
<ListItemIcon>
<RestoreIcon fontSize='small' />
</ListItemIcon>
<ListItemText>Undelete property</ListItemText>
</MenuItem>
) : (
<MenuItem onClick={() => props.onDelete(propertyId)}>
<ListItemIcon>
<DeleteOutlinedIcon fontSize='small' />
</ListItemIcon>
<ListItemText>Delete property</ListItemText>
</MenuItem>
)}
<Menu
{...bindMenu(changePropertyTypePopupState)}
anchorOrigin={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function CustomPropertiesAdapter({ onChange, readOnly, readOnlyProperties
mutator={mutator ?? undefined}
readOnlyProperties={readOnlyProperties}
disableEditPropertyOption={!isAdmin}
isTemplate={!proposalFromDb} // templates are not loaded into the proposalsmap
boardType='proposals'
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export class ProposalsMutator extends Mutator {
});
}

// to enable undeleting properties
async updateProperty(board: Board, propertyId: string, updatedProperty: IPropertyTemplate) {
this.blocksContext.updateProperty({
...updatedProperty
});
}

async reorderProperties(boardId: string, cardProperties: IPropertyTemplate[]): Promise<void> {
const proposalBoardBlock = this.blocksContext.proposalBoardBlock;
const oldFields = proposalBoardBlock?.fields || {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
};
}
return board;
}, [board]);

Check warning on line 71 in components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has missing dependencies: 'getFeatureTitle' and 'reward.sourceProposalPage'. Either include them or remove the dependency array

Check warning on line 71 in components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx

View workflow job for this annotation

GitHub Actions / Test app

React Hook useMemo has missing dependencies: 'getFeatureTitle' and 'reward.sourceProposalPage'. Either include them or remove the dependency array

Check warning on line 71 in components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has missing dependencies: 'getFeatureTitle' and 'reward.sourceProposalPage'. Either include them or remove the dependency array

Check warning on line 71 in components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx

View workflow job for this annotation

GitHub Actions / Validate code

React Hook useMemo has missing dependencies: 'getFeatureTitle' and 'reward.sourceProposalPage'. Either include them or remove the dependency array

if (!boardCustomProperties || !card) {
return null;
Expand All @@ -87,6 +87,7 @@
mutator={mutator ?? undefined}
disableEditPropertyOption={!isAdmin}
boardType='rewards'
isTemplate={rewardPage?.type === 'bounty_template'}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export class RewardsMutator extends Mutator {
});
}

// to enable undeleting properties
async updateProperty(board: Board, propertyId: string, updatedProperty: IPropertyTemplate) {
this.blocksContext.updateProperty({
...updatedProperty
});
}

async reorderProperties(boardId: string, cardProperties: IPropertyTemplate[]): Promise<void> {
const rewardsBoardBlock = this.blocksContext.rewardsBoardBlock;
const oldFields = rewardsBoardBlock?.fields || {};
Expand Down
9 changes: 8 additions & 1 deletion components/rewards/hooks/useRewardsBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
if (updatedRewardBoardBlock) {
mutate();
}
}, []);

Check warning on line 47 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useCallback has a missing dependency: 'mutate'. Either include it or remove the dependency array

Check warning on line 47 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Test app

React Hook useCallback has a missing dependency: 'mutate'. Either include it or remove the dependency array

Check warning on line 47 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useCallback has a missing dependency: 'mutate'. Either include it or remove the dependency array

Check warning on line 47 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Validate code

React Hook useCallback has a missing dependency: 'mutate'. Either include it or remove the dependency array

useEffect(() => {
const unsubscribeFromRewardBlockUpdates = subscribe('reward_blocks_updated', handleRewardBlockUpdates);
Expand All @@ -52,7 +52,7 @@
return () => {
unsubscribeFromRewardBlockUpdates();
};
}, []);

Check warning on line 55 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useEffect has missing dependencies: 'handleRewardBlockUpdates' and 'subscribe'. Either include them or remove the dependency array

Check warning on line 55 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Test app

React Hook useEffect has missing dependencies: 'handleRewardBlockUpdates' and 'subscribe'. Either include them or remove the dependency array

Check warning on line 55 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useEffect has missing dependencies: 'handleRewardBlockUpdates' and 'subscribe'. Either include them or remove the dependency array

Check warning on line 55 in components/rewards/hooks/useRewardsBoard.ts

View workflow job for this annotation

GitHub Actions / Validate code

React Hook useEffect has missing dependencies: 'handleRewardBlockUpdates' and 'subscribe'. Either include them or remove the dependency array

const createProperty = useCallback(
async (propertyTemplate: IPropertyTemplate) => {
Expand Down Expand Up @@ -126,7 +126,14 @@
return;
}

const updatedProperties = dbBlock.fields.cardProperties.filter((p) => p.id !== propertyTemplateId);
const updatedProperties = dbBlock.fields.cardProperties.map((p) =>
p.id === propertyTemplateId
? {
...p,
deletedAt: new Date().toISOString()
}
: p
);
const updatedBlock = {
...dbBlock,
fields: { ...(dbBlock.fields as BoardFields), cardProperties: updatedProperties }
Expand Down
9 changes: 8 additions & 1 deletion hooks/useProposalBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,14 @@ export function ProposalBlocksProvider({ children }: { children: ReactNode }) {
return;
}

const updatedProperties = proposalBoardBlock.fields.cardProperties.filter((p) => p.id !== propertyTemplateId);
const updatedProperties = proposalBoardBlock.fields.cardProperties.map((p) =>
p.id === propertyTemplateId
? {
...p,
deletedAt: new Date().toISOString()
}
: p
);
const updatedBlock = { ...proposalBoardBlock, fields: { cardProperties: updatedProperties } };
try {
const res = await updateProposalBlocks([updatedBlock]);
Expand Down
1 change: 1 addition & 0 deletions lib/databases/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export type IPropertyTemplate<T extends PropertyType = PropertyType> = {
id: string;
name: string;
type: T;
deletedAt?: string; // ISODate. If set, the property will be visible based on when the page was created
options: IPropertyOption[];
description?: string;
tooltip?: string;
Expand Down
22 changes: 20 additions & 2 deletions lib/proposals/createDraftProposal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PageType } from '@charmverse/core/prisma';
import { prisma } from '@charmverse/core/prisma-client';
import type { WorkflowEvaluationJson } from '@charmverse/core/proposals';
import type { BoardFields } from '@root/lib/databases/board';
import { generatePagePathFromPathAndTitle } from '@root/lib/pages/utils';
import { createDefaultProjectAndMembersFieldConfig } from '@root/lib/projects/formField';
import type { FormFieldInput } from '@root/lib/proposals/forms/interfaces';
Expand Down Expand Up @@ -31,7 +32,7 @@ export type CreateDraftProposalInput = {

export async function createDraftProposal(input: CreateDraftProposalInput) {
// get source data
const [template, sourcePage, sourcePost] = await Promise.all([
const [template, sourcePage, sourcePost, proposalBoardBlock] = await Promise.all([
input.templateId
? await prisma.page.findUniqueOrThrow({
where: {
Expand Down Expand Up @@ -76,7 +77,15 @@ export async function createDraftProposal(input: CreateDraftProposalInput) {
id: input.sourcePostId
}
})
: null
: null,
prisma.proposalBlock.findUnique({
where: {
id_spaceId: {
id: '__defaultBoard',
spaceId: input.spaceId
}
}
})
]);

const workflow =
Expand Down Expand Up @@ -130,6 +139,15 @@ export async function createDraftProposal(input: CreateDraftProposalInput) {
pendingRewards: []
};

// dont create properties that were deleted
if (proposalBoardBlock && fields.properties) {
(proposalBoardBlock.fields as unknown as BoardFields).cardProperties.forEach((property) => {
if (property.deletedAt) {
delete fields.properties![property.id];
}
});
}

let formFields: FormFieldInput[] = [];
if (input.pageType === 'proposal_template' && template?.proposal?.form) {
formFields = template.proposal.form.formFields.map(
Expand Down
21 changes: 21 additions & 0 deletions lib/rewards/createReward.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { Page, PagePermission, Prisma } from '@charmverse/core/prisma';
import { prisma } from '@charmverse/core/prisma-client';
import type { BoardFields } from '@root/lib/databases/board';
import { NotFoundError } from '@root/lib/middleware';
import { generatePagePathFromPathAndTitle } from '@root/lib/pages/utils';
import { extractPreviewImage } from '@root/lib/prosemirror/extractPreviewImage';
import { InvalidInputError } from '@root/lib/utils/errors';
import { v4 } from 'uuid';

import type { RewardFields } from 'lib/rewards/blocks/interfaces';

import { trackOpUserAction } from '../metrics/mixpanel/trackOpUserAction';

import { getRewardOrThrow } from './getReward';
Expand Down Expand Up @@ -82,6 +85,24 @@ export async function createReward({
}
});

const rewardBoardBlock = await prisma.rewardBlock.findUnique({
where: {
id_spaceId: {
id: '__defaultBoard',
spaceId
}
}
});

// dont create properties that were deleted
if (rewardBoardBlock && (fields as unknown as RewardFields | undefined)?.properties) {
(rewardBoardBlock.fields as unknown as BoardFields).cardProperties.forEach((property) => {
if (property.deletedAt) {
delete (fields as unknown as RewardFields).properties![property.id];
}
});
}

const rewardId = v4();

const isAssignedReward = Array.isArray(assignedSubmitters) && assignedSubmitters.length > 0;
Expand Down
Loading
Loading