Skip to content

Commit

Permalink
feat: review pull request modal window (DAP-4764) (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
alsakhaev authored Oct 4, 2024
1 parent a83ed7d commit f59d5ed
Show file tree
Hide file tree
Showing 14 changed files with 1,316 additions and 118 deletions.
1 change: 1 addition & 0 deletions apps/extension/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = {
path: path.join(__dirname, 'build'),
filename: '[name].js',
publicPath: '',
chunkFormat: false
},
module: {
rules: [
Expand Down
9 changes: 9 additions & 0 deletions apps/extension/webpack.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ module.exports = merge(common, {
minimizer: [
new TerserPlugin({
extractComments: false,
parallel: true,
// fixes codemirror
// https://stackoverflow.com/questions/49979397/chrome-says-my-content-script-isnt-utf-8
terserOptions: {
ecma: 6,
output: {
ascii_only: true,
},
},
}),
],
},
Expand Down
2 changes: 2 additions & 0 deletions libs/engine/src/app/contexts/mutable-web-context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export { useMutableWeb } from './use-mutable-web'
export { useMutationApp } from './use-mutation-app'
export { useCreateMutation } from './use-create-mutation'
export { useEditMutation } from './use-edit-mutation'
export { useMutations } from './use-mutations'
export { useMutation } from './use-mutation'
14 changes: 14 additions & 0 deletions libs/engine/src/app/contexts/mutable-web-context/use-mutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MutableWebContext } from './mutable-web-context'
import { MutationId } from '../../services/mutation/mutation.entity'
import { useContext, useMemo } from 'react'

export const useMutation = (mutationId: MutationId) => {
const { mutations } = useContext(MutableWebContext)

const mutation = useMemo(
() => mutations.find((mutation) => mutation.id === mutationId) ?? null,
[mutations, mutationId]
)

return { mutation }
}
9 changes: 8 additions & 1 deletion libs/engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export { IStorage } from './app/services/local-db/local-storage'
export { App } from './app/app'
export { useEngine } from './app/contexts/engine-context'
export {
useMutation,
useMutations,
useMutableWeb,
useCreateMutation,
useEditMutation,
Expand All @@ -35,5 +37,10 @@ export {
} from './app/contexts/notification-context'
export { NotificationType } from './app/services/notification/notification.entity'
export { NotificationDto } from './app/services/notification/dtos/notification.dto'
export { PullRequestPayload, PullRequestResult, PullRequestStatus } from './app/services/notification/types/pull-request'
export { BaseDto } from './app/services/base/base.dto'
export {
PullRequestPayload,
PullRequestResult,
PullRequestStatus,
} from './app/services/notification/types/pull-request'
export { RegularPayload } from './app/services/notification/types/regular'
9 changes: 8 additions & 1 deletion libs/shared-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@
"styled-components": "^5.3.6"
},
"dependencies": {
"@codemirror/state": "^6.4.1",
"@mweb/engine": "workspace:*",
"@uiw/codemirror-extensions-langs": "^4.23.3",
"@uiw/react-codemirror": "^4.23.3",
"antd": "^5.18.3",
"codemirror": "^6.0.1",
"ethereum-blockies-base64": "^1.0.2",
"antd": "^5.18.3"
"json-stringify-deterministic": "^1.0.12",
"react-codemirror-merge": "^4.23.3",
"react-diff-viewer": "^3.1.1"
}
}
3 changes: 2 additions & 1 deletion libs/shared-components/src/mini-overlay/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppWithSettings, MutationDto } from '@mweb/engine'
import { useAccountId } from 'near-social-vm'
import React, { FC, ReactElement, useState, useRef } from 'react'
import React, { FC, ReactElement, useState, useRef, useEffect } from 'react'
import Spinner from 'react-bootstrap/Spinner'
import styled from 'styled-components'
import { Image } from '../common/image'
Expand Down Expand Up @@ -244,6 +244,7 @@ export const MiniOverlay: FC<IMiniOverlayProps> = ({
open={open}
connectWallet={connectWallet}
loggedInAccountId={loggedInAccountId}
modalContainerRef={overlayRef}
/>
</NotificationProvider>
</WrapperDriver>
Expand Down
3 changes: 3 additions & 0 deletions libs/shared-components/src/mini-overlay/overlay-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export interface IOverlayWrapperProps {
open: boolean
loggedInAccountId: string
connectWallet: (() => Promise<void>) | undefined
modalContainerRef: React.RefObject<HTMLElement>
}

const OverlayWrapper: FC<IOverlayWrapperProps> = ({
Expand All @@ -146,6 +147,7 @@ const OverlayWrapper: FC<IOverlayWrapperProps> = ({
open,
loggedInAccountId,
connectWallet,
modalContainerRef,
}) => {
const overlayRef = useRef<HTMLDivElement>(null)

Expand Down Expand Up @@ -182,6 +184,7 @@ const OverlayWrapper: FC<IOverlayWrapperProps> = ({
<NotificationFeed
connectWallet={connectWallet}
loggedInAccountId={loggedInAccountId}
modalContainerRef={modalContainerRef}
/>
</Body>
}
Expand Down
19 changes: 15 additions & 4 deletions libs/shared-components/src/notifications/notification-feed.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useNotifications, useViewAllNotifications } from '@mweb/engine'
import { NotificationDto, useNotifications, useViewAllNotifications } from '@mweb/engine'
import React, { FC, useMemo, useRef, useState } from 'react'
import NotificationsResolver from './notification-resolver'
import { Space, Typography, Button, Spin, Flex } from 'antd'
import styled from 'styled-components'
import { PrReviewerModal } from './pr-reviewer-modal'

const { Text } = Typography

const FeedContainer = styled(Space)`
Expand Down Expand Up @@ -37,7 +39,8 @@ const Loader = () => (
const NotificationFeed: FC<{
loggedInAccountId: string
connectWallet: (() => Promise<void>) | undefined
}> = ({ loggedInAccountId, connectWallet }) => {
modalContainerRef: React.RefObject<HTMLElement>
}> = ({ loggedInAccountId, connectWallet, modalContainerRef }) => {
const [isWaiting, setWaiting] = useState(false)
const { notifications, isLoading } = useNotifications()
const overlayRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -97,7 +100,11 @@ const NotificationFeed: FC<{
</Space>
<SmoothSpace direction="vertical">
{newNotifications.map((notification) => (
<NotificationsResolver key={notification.id} notification={notification} />
<NotificationsResolver
key={notification.id}
notification={notification}
modalContainerRef={modalContainerRef}
/>
))}
</SmoothSpace>
</Flex>
Expand All @@ -109,7 +116,11 @@ const NotificationFeed: FC<{
</Space>
<SmoothSpace direction="vertical">
{viewedNotifications.map((notification) => (
<NotificationsResolver key={notification.id} notification={notification} />
<NotificationsResolver
key={notification.id}
notification={notification}
modalContainerRef={modalContainerRef}
/>
))}
</SmoothSpace>
</Flex>
Expand Down
12 changes: 10 additions & 2 deletions libs/shared-components/src/notifications/notification-resolver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import RegularNotification, { RegularNotificationDto } from './regular-notificat
import PullRequestNotification, { PullRequestNotificationDto } from './pull-request-notification'
import { NotificationDto, NotificationType } from '@mweb/engine'

const NotificationsResolver: FC<{ notification: NotificationDto }> = ({ notification }) => {
const NotificationsResolver: FC<{
notification: NotificationDto
modalContainerRef: React.RefObject<HTMLElement>
}> = ({ notification, modalContainerRef }) => {
switch (notification.type) {
case NotificationType.Regular:
return <RegularNotification notification={notification as RegularNotificationDto} />
case NotificationType.PullRequest:
return <PullRequestNotification notification={notification as PullRequestNotificationDto} />
return (
<PullRequestNotification
notification={notification as PullRequestNotificationDto}
modalContainerRef={modalContainerRef}
/>
)
default:
return null
}
Expand Down
96 changes: 96 additions & 0 deletions libs/shared-components/src/notifications/pr-reviewer-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { FC, useMemo } from 'react'
import { Button, Modal } from 'antd'
import toJson from 'json-stringify-deterministic'
import {
NotificationDto,
NotificationType,
PullRequestPayload,
useAcceptPullRequest,
useMutation,
useRejectPullRequest,
} from '@mweb/engine'
import { PrReviewer } from './pr-reviewer'
import { IconBranchButton, IconNotificationClose } from './test-data-notification'

const leaveMergableProps = (mutation: any): any => {
return {
apps: mutation.apps,
targets: mutation.targets,
metadata: {
description: mutation.metadata.description,
},
}
}

export interface Props {
notification: NotificationDto
containerRef: React.RefObject<HTMLElement>
onClose: () => void
}

export const PrReviewerModal: FC<Props> = ({ notification, containerRef, onClose }) => {
if (notification.type !== NotificationType.PullRequest) {
throw new Error('Only PullRequest notifications are supported')
}

const { sourceMutationId, targetMutationId } = notification.payload as PullRequestPayload

const { mutation: source } = useMutation(sourceMutationId)
const { mutation: target } = useMutation(targetMutationId)

const { acceptPullRequest, isLoading: isLoadingAccept } = useAcceptPullRequest(notification.id)
const { rejectPullRequest, isLoading: isLoadingReject } = useRejectPullRequest(notification.id)

const sourceJson = useMemo(() => toJson(leaveMergableProps(source), { space: ' ' }), [])
const targetJson = useMemo(() => toJson(leaveMergableProps(target), { space: ' ' }), [])

const handleAcceptClick = () => {
// ToDo: replace .then() with useEffect?
acceptPullRequest().then(() => onClose())
}

const handleDeclineClick = () => {
// ToDo: replace .then() with useEffect?
rejectPullRequest().then(() => onClose())
}

return (
<Modal
title="Review Changes"
open
centered
getContainer={containerRef.current ?? false}
zIndex={10000}
onCancel={onClose}
width={1000}
footer={[
<Button
key="decline"
loading={isLoadingReject}
disabled={isLoadingAccept || isLoadingReject}
type="default"
size="middle"
onClick={handleDeclineClick}
icon={<IconNotificationClose />}
iconPosition="start"
>
Decline
</Button>,
<Button
key="accept"
loading={isLoadingAccept}
disabled={isLoadingAccept || isLoadingReject}
type="primary"
size="middle"
onClick={handleAcceptClick}
icon={<IconBranchButton />}
iconPosition="start"
>
Accept
</Button>,
]}
>
<PrReviewer modifiedCode={sourceJson} originalCode={targetJson} />
</Modal>
)
}
55 changes: 55 additions & 0 deletions libs/shared-components/src/notifications/pr-reviewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useEffect, useRef } from 'react'
import { FC } from 'react'
import CodeMirrorMerge from 'react-codemirror-merge'
import { langs } from '@uiw/codemirror-extensions-langs'

export interface Props {
originalCode: string
modifiedCode: string
}

export const PrReviewer: FC<Props> = ({ originalCode, modifiedCode }) => {
const containerRef = useRef<HTMLDivElement>(null)

// ToDo: workaround that moves styles from head to shadow dom
useEffect(() => {
const observer = new MutationObserver(() => {
const styleElement = document.evaluate(
"//head/style[contains(text(),'cm-')]",
document.head,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue

if (styleElement) {
setTimeout(() => {
containerRef.current!.append(styleElement)
}, 10)
}
})

observer.observe(document.head, { childList: true })

return () => {
observer.disconnect()
}
}, [])

return (
<div ref={containerRef} style={{ overflowY: 'scroll', maxHeight: 630 }}>
<CodeMirrorMerge>
<CodeMirrorMerge.Original
value={originalCode}
editable={false}
extensions={[langs.json()]}
/>
<CodeMirrorMerge.Modified
value={modifiedCode}
editable={false}
extensions={[langs.json()]}
/>
</CodeMirrorMerge>
</div>
)
}
Loading

0 comments on commit f59d5ed

Please sign in to comment.