mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	add tabs to milestone start (#10237)
Adds changes/view diff tabs to release plan changes that show diffs. The only instances I found where we show JSON diffs today was starting milestones and adding a new release plan if you already have one. I've moved the old file into a legacy file because we're touching two out of three internal components, so it seemed like leaving it all in one file would be a bit of a hassle. plus, this way it's consistent with segments and strategies. Start milestone: <img width="1035" alt="image" src="https://github.com/user-attachments/assets/2b4616f6-8452-4976-8101-11a94d6d5828" /> <img width="1054" alt="image" src="https://github.com/user-attachments/assets/0ba58c72-b3dc-48fa-95bf-a3980dc620fe" /> Plan replacement: <img width="1006" alt="image" src="https://github.com/user-attachments/assets/9381a48f-e23e-435e-8fa5-02fcb5050bfd" /> <img width="818" alt="image" src="https://github.com/user-attachments/assets/c5ceb9db-b095-4d05-88e8-fd8a70776479" />
This commit is contained in:
		
							parent
							
								
									fdc79e624f
								
							
						
					
					
						commit
						7c0bd12a24
					
				| @ -14,6 +14,7 @@ import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutio | ||||
| import { ArchiveFeatureChange } from './ArchiveFeatureChange.tsx'; | ||||
| import { DependencyChange } from './DependencyChange.tsx'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { LegacyReleasePlanChange } from './LegacyReleasePlanChange.tsx'; | ||||
| import { ReleasePlanChange } from './ReleasePlanChange.tsx'; | ||||
| import { StrategyChange } from './StrategyChange.tsx'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag.ts'; | ||||
| @ -94,6 +95,10 @@ export const FeatureChange: FC<{ | ||||
|         ? StrategyChange | ||||
|         : LegacyStrategyChange; | ||||
| 
 | ||||
|     const ReleasePlanChangeComponent = useDiffableChangeComponent | ||||
|         ? ReleasePlanChange | ||||
|         : LegacyReleasePlanChange; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledSingleChangeBox | ||||
|             key={objectId(change)} | ||||
| @ -204,7 +209,7 @@ export const FeatureChange: FC<{ | ||||
|                 {(change.action === 'addReleasePlan' || | ||||
|                     change.action === 'deleteReleasePlan' || | ||||
|                     change.action === 'startMilestone') && ( | ||||
|                     <ReleasePlanChange | ||||
|                     <ReleasePlanChangeComponent | ||||
|                         actions={actions} | ||||
|                         change={change} | ||||
|                         featureName={feature.name} | ||||
|  | ||||
| @ -0,0 +1,316 @@ | ||||
| import type React from 'react'; | ||||
| import { useRef, useState, type FC, type ReactNode } from 'react'; | ||||
| import { Box, styled, Typography } from '@mui/material'; | ||||
| import type { | ||||
|     ChangeRequestState, | ||||
|     IChangeRequestAddReleasePlan, | ||||
|     IChangeRequestDeleteReleasePlan, | ||||
|     IChangeRequestStartMilestone, | ||||
| } from 'component/changeRequest/changeRequest.types'; | ||||
| import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview'; | ||||
| import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; | ||||
| import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; | ||||
| import EventDiff from 'component/events/EventDiff/EventDiff'; | ||||
| import { ReleasePlan } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan'; | ||||
| import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone'; | ||||
| import type { IReleasePlan } from 'interfaces/releasePlans'; | ||||
| 
 | ||||
| export const ChangeItemWrapper = styled(Box)({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'space-between', | ||||
|     alignItems: 'center', | ||||
| }); | ||||
| 
 | ||||
| const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({ | ||||
|     display: 'grid', | ||||
|     gridTemplateColumns: 'auto auto', | ||||
|     justifyContent: 'space-between', | ||||
|     gap: theme.spacing(1), | ||||
|     alignItems: 'center', | ||||
|     marginBottom: theme.spacing(2), | ||||
|     width: '100%', | ||||
| })); | ||||
| 
 | ||||
| const ChangeItemInfo: FC<{ children?: React.ReactNode }> = styled(Box)( | ||||
|     ({ theme }) => ({ | ||||
|         display: 'flex', | ||||
|         gap: theme.spacing(1), | ||||
|     }), | ||||
| ); | ||||
| 
 | ||||
| const ViewDiff = styled('span')(({ theme }) => ({ | ||||
|     color: theme.palette.primary.main, | ||||
|     marginLeft: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyledCodeSection = styled('div')(({ theme }) => ({ | ||||
|     overflowX: 'auto', | ||||
|     '& code': { | ||||
|         wordWrap: 'break-word', | ||||
|         whiteSpace: 'pre-wrap', | ||||
|         fontFamily: 'monospace', | ||||
|         lineHeight: 1.5, | ||||
|         fontSize: theme.fontSizes.smallBody, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const DeleteReleasePlan: FC<{ | ||||
|     change: IChangeRequestDeleteReleasePlan; | ||||
|     currentReleasePlan?: IReleasePlan; | ||||
|     changeRequestState: ChangeRequestState; | ||||
|     actions?: ReactNode; | ||||
| }> = ({ change, currentReleasePlan, changeRequestState, actions }) => { | ||||
|     const releasePlan = | ||||
|         changeRequestState === 'Applied' && change.payload.snapshot | ||||
|             ? change.payload.snapshot | ||||
|             : currentReleasePlan; | ||||
| 
 | ||||
|     if (!releasePlan) return; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ChangeItemInfo> | ||||
|                     <Typography | ||||
|                         sx={(theme) => ({ | ||||
|                             color: theme.palette.error.main, | ||||
|                         })} | ||||
|                     > | ||||
|                         - Deleting release plan: | ||||
|                     </Typography> | ||||
|                     <Typography>{releasePlan.name}</Typography> | ||||
|                 </ChangeItemInfo> | ||||
|                 <div>{actions}</div> | ||||
|             </ChangeItemCreateEditDeleteWrapper> | ||||
|             <ReleasePlan plan={releasePlan} readonly /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const StartMilestone: FC<{ | ||||
|     change: IChangeRequestStartMilestone; | ||||
|     currentReleasePlan?: IReleasePlan; | ||||
|     changeRequestState: ChangeRequestState; | ||||
|     actions?: ReactNode; | ||||
| }> = ({ change, currentReleasePlan, changeRequestState, actions }) => { | ||||
|     const releasePlan = | ||||
|         changeRequestState === 'Applied' && change.payload.snapshot | ||||
|             ? change.payload.snapshot | ||||
|             : currentReleasePlan; | ||||
| 
 | ||||
|     if (!releasePlan) return; | ||||
| 
 | ||||
|     const previousMilestone = releasePlan.milestones.find( | ||||
|         (milestone) => milestone.id === releasePlan.activeMilestoneId, | ||||
|     ); | ||||
| 
 | ||||
|     const newMilestone = releasePlan.milestones.find( | ||||
|         (milestone) => milestone.id === change.payload.milestoneId, | ||||
|     ); | ||||
| 
 | ||||
|     if (!newMilestone) return; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ChangeItemInfo> | ||||
|                     <Typography color='success.dark'> | ||||
|                         + Start milestone: | ||||
|                     </Typography> | ||||
|                     <Typography>{newMilestone.name}</Typography> | ||||
|                     <TooltipLink | ||||
|                         tooltip={ | ||||
|                             <StyledCodeSection> | ||||
|                                 <EventDiff | ||||
|                                     entry={{ | ||||
|                                         preData: previousMilestone, | ||||
|                                         data: newMilestone, | ||||
|                                     }} | ||||
|                                 /> | ||||
|                             </StyledCodeSection> | ||||
|                         } | ||||
|                         tooltipProps={{ | ||||
|                             maxWidth: 500, | ||||
|                             maxHeight: 600, | ||||
|                         }} | ||||
|                     > | ||||
|                         <ViewDiff>View Diff</ViewDiff> | ||||
|                     </TooltipLink> | ||||
|                 </ChangeItemInfo> | ||||
|                 <div>{actions}</div> | ||||
|             </ChangeItemCreateEditDeleteWrapper> | ||||
|             <ReleasePlanMilestone readonly milestone={newMilestone} /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const AddReleasePlan: FC<{ | ||||
|     change: IChangeRequestAddReleasePlan; | ||||
|     currentReleasePlan?: IReleasePlan; | ||||
|     environmentName: string; | ||||
|     featureName: string; | ||||
|     actions?: ReactNode; | ||||
| }> = ({ | ||||
|     change, | ||||
|     currentReleasePlan, | ||||
|     environmentName, | ||||
|     featureName, | ||||
|     actions, | ||||
| }) => { | ||||
|     const [currentTooltipOpen, setCurrentTooltipOpen] = useState(false); | ||||
|     const currentTooltipCloseTimeoutRef = useRef<NodeJS.Timeout>(); | ||||
|     const openCurrentTooltip = () => { | ||||
|         if (currentTooltipCloseTimeoutRef.current) { | ||||
|             clearTimeout(currentTooltipCloseTimeoutRef.current); | ||||
|         } | ||||
|         setCurrentTooltipOpen(true); | ||||
|     }; | ||||
|     const closeCurrentTooltip = () => { | ||||
|         currentTooltipCloseTimeoutRef.current = setTimeout(() => { | ||||
|             setCurrentTooltipOpen(false); | ||||
|         }, 100); | ||||
|     }; | ||||
| 
 | ||||
|     const planPreview = useReleasePlanPreview( | ||||
|         change.payload.templateId, | ||||
|         featureName, | ||||
|         environmentName, | ||||
|     ); | ||||
| 
 | ||||
|     const planPreviewDiff = { | ||||
|         ...planPreview, | ||||
|         discriminator: 'plan', | ||||
|         releasePlanTemplateId: change.payload.templateId, | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ChangeItemInfo> | ||||
|                     {currentReleasePlan ? ( | ||||
|                         <Typography> | ||||
|                             Replacing{' '} | ||||
|                             <TooltipLink | ||||
|                                 tooltip={ | ||||
|                                     <div | ||||
|                                         onMouseEnter={() => | ||||
|                                             openCurrentTooltip() | ||||
|                                         } | ||||
|                                         onMouseLeave={() => | ||||
|                                             closeCurrentTooltip() | ||||
|                                         } | ||||
|                                     > | ||||
|                                         <ReleasePlan | ||||
|                                             plan={currentReleasePlan} | ||||
|                                             readonly | ||||
|                                         /> | ||||
|                                     </div> | ||||
|                                 } | ||||
|                                 tooltipProps={{ | ||||
|                                     open: currentTooltipOpen, | ||||
|                                     maxWidth: 500, | ||||
|                                     maxHeight: 600, | ||||
|                                 }} | ||||
|                             > | ||||
|                                 <span | ||||
|                                     onMouseEnter={() => openCurrentTooltip()} | ||||
|                                     onMouseLeave={() => closeCurrentTooltip()} | ||||
|                                 > | ||||
|                                     current | ||||
|                                 </span> | ||||
|                             </TooltipLink>{' '} | ||||
|                             release plan with: | ||||
|                         </Typography> | ||||
|                     ) : ( | ||||
|                         <Typography color='success.dark'> | ||||
|                             + Adding release plan: | ||||
|                         </Typography> | ||||
|                     )} | ||||
|                     <Typography>{planPreview.name}</Typography> | ||||
|                     {currentReleasePlan && ( | ||||
|                         <TooltipLink | ||||
|                             tooltip={ | ||||
|                                 <StyledCodeSection> | ||||
|                                     <EventDiff | ||||
|                                         entry={{ | ||||
|                                             preData: currentReleasePlan, | ||||
|                                             data: planPreviewDiff, | ||||
|                                         }} | ||||
|                                     /> | ||||
|                                 </StyledCodeSection> | ||||
|                             } | ||||
|                             tooltipProps={{ | ||||
|                                 maxWidth: 500, | ||||
|                                 maxHeight: 600, | ||||
|                             }} | ||||
|                         > | ||||
|                             <ViewDiff>View Diff</ViewDiff> | ||||
|                         </TooltipLink> | ||||
|                     )} | ||||
|                 </ChangeItemInfo> | ||||
|                 <div>{actions}</div> | ||||
|             </ChangeItemCreateEditDeleteWrapper> | ||||
|             <ReleasePlan plan={planPreview} readonly /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Deprecated: use ReleasePlanChange instead. Remove file with flag crDiffView | ||||
|  * @deprecated | ||||
|  */ | ||||
| export const LegacyReleasePlanChange: FC<{ | ||||
|     actions?: ReactNode; | ||||
|     change: | ||||
|         | IChangeRequestAddReleasePlan | ||||
|         | IChangeRequestDeleteReleasePlan | ||||
|         | IChangeRequestStartMilestone; | ||||
|     environmentName: string; | ||||
|     featureName: string; | ||||
|     projectId: string; | ||||
|     changeRequestState: ChangeRequestState; | ||||
| }> = ({ | ||||
|     actions, | ||||
|     change, | ||||
|     featureName, | ||||
|     environmentName, | ||||
|     projectId, | ||||
|     changeRequestState, | ||||
| }) => { | ||||
|     const { releasePlans } = useReleasePlans( | ||||
|         projectId, | ||||
|         featureName, | ||||
|         environmentName, | ||||
|     ); | ||||
|     const currentReleasePlan = releasePlans[0]; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             {change.action === 'addReleasePlan' && ( | ||||
|                 <AddReleasePlan | ||||
|                     change={change} | ||||
|                     currentReleasePlan={currentReleasePlan} | ||||
|                     environmentName={environmentName} | ||||
|                     featureName={featureName} | ||||
|                     actions={actions} | ||||
|                 /> | ||||
|             )} | ||||
|             {change.action === 'deleteReleasePlan' && ( | ||||
|                 <DeleteReleasePlan | ||||
|                     change={change} | ||||
|                     currentReleasePlan={currentReleasePlan} | ||||
|                     changeRequestState={changeRequestState} | ||||
|                     actions={actions} | ||||
|                 /> | ||||
|             )} | ||||
|             {change.action === 'startMilestone' && ( | ||||
|                 <StartMilestone | ||||
|                     change={change} | ||||
|                     currentReleasePlan={currentReleasePlan} | ||||
|                     changeRequestState={changeRequestState} | ||||
|                     actions={actions} | ||||
|                 /> | ||||
|             )} | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| @ -10,10 +10,11 @@ import type { | ||||
| import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview'; | ||||
| import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; | ||||
| import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; | ||||
| import EventDiff from 'component/events/EventDiff/EventDiff'; | ||||
| import { EventDiff } from 'component/events/EventDiff/EventDiff'; | ||||
| import { ReleasePlan } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan'; | ||||
| import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone'; | ||||
| import type { IReleasePlan } from 'interfaces/releasePlans'; | ||||
| import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx'; | ||||
| 
 | ||||
| export const ChangeItemWrapper = styled(Box)({ | ||||
|     display: 'flex', | ||||
| @ -111,36 +112,34 @@ const StartMilestone: FC<{ | ||||
|     if (!newMilestone) return; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|         <Tabs> | ||||
|             <ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ChangeItemInfo> | ||||
|                     <Typography color='success.dark'> | ||||
|                         + Start milestone: | ||||
|                     </Typography> | ||||
|                     <Typography>{newMilestone.name}</Typography> | ||||
|                     <TooltipLink | ||||
|                         tooltip={ | ||||
|                             <StyledCodeSection> | ||||
|                                 <EventDiff | ||||
|                                     entry={{ | ||||
|                                         preData: previousMilestone, | ||||
|                                         data: newMilestone, | ||||
|                                     }} | ||||
|                                 /> | ||||
|                             </StyledCodeSection> | ||||
|                         } | ||||
|                         tooltipProps={{ | ||||
|                             maxWidth: 500, | ||||
|                             maxHeight: 600, | ||||
|                         }} | ||||
|                     > | ||||
|                         <ViewDiff>View Diff</ViewDiff> | ||||
|                     </TooltipLink> | ||||
|                 </ChangeItemInfo> | ||||
|                 <div>{actions}</div> | ||||
|                 <div> | ||||
|                     <TabList> | ||||
|                         <Tab>Change</Tab> | ||||
|                         <Tab>View diff</Tab> | ||||
|                     </TabList> | ||||
|                     {actions} | ||||
|                 </div> | ||||
|             </ChangeItemCreateEditDeleteWrapper> | ||||
|             <ReleasePlanMilestone readonly milestone={newMilestone} /> | ||||
|         </> | ||||
|             <TabPanel> | ||||
|                 <ReleasePlanMilestone readonly milestone={newMilestone} /> | ||||
|             </TabPanel> | ||||
|             <TabPanel> | ||||
|                 <EventDiff | ||||
|                     entry={{ | ||||
|                         preData: previousMilestone, | ||||
|                         data: newMilestone, | ||||
|                     }} | ||||
|                 /> | ||||
|             </TabPanel> | ||||
|         </Tabs> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| @ -183,75 +182,78 @@ const AddReleasePlan: FC<{ | ||||
|         releasePlanTemplateId: change.payload.templateId, | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ChangeItemInfo> | ||||
|                     {currentReleasePlan ? ( | ||||
|                         <Typography> | ||||
|                             Replacing{' '} | ||||
|                             <TooltipLink | ||||
|                                 tooltip={ | ||||
|                                     <div | ||||
|                                         onMouseEnter={() => | ||||
|                                             openCurrentTooltip() | ||||
|                                         } | ||||
|                                         onMouseLeave={() => | ||||
|                                             closeCurrentTooltip() | ||||
|                                         } | ||||
|                                     > | ||||
|                                         <ReleasePlan | ||||
|                                             plan={currentReleasePlan} | ||||
|                                             readonly | ||||
|                                         /> | ||||
|                                     </div> | ||||
|                                 } | ||||
|                                 tooltipProps={{ | ||||
|                                     open: currentTooltipOpen, | ||||
|                                     maxWidth: 500, | ||||
|                                     maxHeight: 600, | ||||
|                                 }} | ||||
|                             > | ||||
|                                 <span | ||||
|                                     onMouseEnter={() => openCurrentTooltip()} | ||||
|                                     onMouseLeave={() => closeCurrentTooltip()} | ||||
|                                 > | ||||
|                                     current | ||||
|                                 </span> | ||||
|                             </TooltipLink>{' '} | ||||
|                             release plan with: | ||||
|                         </Typography> | ||||
|                     ) : ( | ||||
|     if (!currentReleasePlan) { | ||||
|         return ( | ||||
|             <> | ||||
|                 <ChangeItemCreateEditDeleteWrapper> | ||||
|                     <ChangeItemInfo> | ||||
|                         <Typography color='success.dark'> | ||||
|                             + Adding release plan: | ||||
|                         </Typography> | ||||
|                     )} | ||||
|                     <Typography>{planPreview.name}</Typography> | ||||
|                     {currentReleasePlan && ( | ||||
|                         <Typography>{planPreview.name}</Typography> | ||||
|                     </ChangeItemInfo> | ||||
|                     <div>{actions}</div> | ||||
|                 </ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ReleasePlan plan={planPreview} readonly /> | ||||
|             </> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <Tabs> | ||||
|             <ChangeItemCreateEditDeleteWrapper> | ||||
|                 <ChangeItemInfo> | ||||
|                     <Typography> | ||||
|                         Replacing{' '} | ||||
|                         <TooltipLink | ||||
|                             tooltip={ | ||||
|                                 <StyledCodeSection> | ||||
|                                     <EventDiff | ||||
|                                         entry={{ | ||||
|                                             preData: currentReleasePlan, | ||||
|                                             data: planPreviewDiff, | ||||
|                                         }} | ||||
|                                 <div | ||||
|                                     onMouseEnter={() => openCurrentTooltip()} | ||||
|                                     onMouseLeave={() => closeCurrentTooltip()} | ||||
|                                 > | ||||
|                                     <ReleasePlan | ||||
|                                         plan={currentReleasePlan} | ||||
|                                         readonly | ||||
|                                     /> | ||||
|                                 </StyledCodeSection> | ||||
|                                 </div> | ||||
|                             } | ||||
|                             tooltipProps={{ | ||||
|                                 open: currentTooltipOpen, | ||||
|                                 maxWidth: 500, | ||||
|                                 maxHeight: 600, | ||||
|                             }} | ||||
|                         > | ||||
|                             <ViewDiff>View Diff</ViewDiff> | ||||
|                         </TooltipLink> | ||||
|                     )} | ||||
|                             <span | ||||
|                                 onMouseEnter={() => openCurrentTooltip()} | ||||
|                                 onMouseLeave={() => closeCurrentTooltip()} | ||||
|                             > | ||||
|                                 current | ||||
|                             </span> | ||||
|                         </TooltipLink>{' '} | ||||
|                         release plan with: | ||||
|                     </Typography> | ||||
|                     <Typography>{planPreview.name}</Typography> | ||||
|                 </ChangeItemInfo> | ||||
|                 <div>{actions}</div> | ||||
|                 <div> | ||||
|                     <TabList> | ||||
|                         <Tab>Changes</Tab> | ||||
|                         <Tab>View diff</Tab> | ||||
|                     </TabList> | ||||
|                     {actions} | ||||
|                 </div> | ||||
|             </ChangeItemCreateEditDeleteWrapper> | ||||
|             <ReleasePlan plan={planPreview} readonly /> | ||||
|         </> | ||||
|             <TabPanel> | ||||
|                 <ReleasePlan plan={planPreview} readonly /> | ||||
|             </TabPanel> | ||||
|             <TabPanel> | ||||
|                 <EventDiff | ||||
|                     entry={{ | ||||
|                         preData: currentReleasePlan, | ||||
|                         data: planPreviewDiff, | ||||
|                     }} | ||||
|                 /> | ||||
|             </TabPanel> | ||||
|         </Tabs> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user