From bce56f63c7a64673312be1bf9c954b625f032fc7 Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Wed, 6 Sep 2023 13:53:33 +0930 Subject: [PATCH 01/26] prevent capitalising input text, we only want to capitalise placeholder --- package.json | 2 +- src/components/PertRowsForm/PertRowsForm.module.css | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 504a8e9..e7a2f0e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pert-with-wings", "private": true, - "version": "1.4", + "version": "1.4.1", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/PertRowsForm/PertRowsForm.module.css b/src/components/PertRowsForm/PertRowsForm.module.css index bf3a46b..ff77ca9 100644 --- a/src/components/PertRowsForm/PertRowsForm.module.css +++ b/src/components/PertRowsForm/PertRowsForm.module.css @@ -59,6 +59,9 @@ z-index: 1; font-size: 1rem; background: var(--pert-field-bg); +} + +.field::placeholder { text-transform: capitalize; } From 298997bff19e15d7c0e4a19d3f29c0d3dd14bede Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Mon, 8 Apr 2024 16:09:34 +0930 Subject: [PATCH 02/26] PERT-48: add common component to display action buttons --- src/components/ActionButton/ActionButton.tsx | 36 ++++++++++++++++++++ src/components/ActionButton/index.ts | 1 + 2 files changed, 37 insertions(+) create mode 100644 src/components/ActionButton/ActionButton.tsx create mode 100644 src/components/ActionButton/index.ts diff --git a/src/components/ActionButton/ActionButton.tsx b/src/components/ActionButton/ActionButton.tsx new file mode 100644 index 0000000..3e45844 --- /dev/null +++ b/src/components/ActionButton/ActionButton.tsx @@ -0,0 +1,36 @@ +import { FC, Fragment, SyntheticEvent, useState } from 'react'; +import { MdCheckCircle } from 'react-icons/md'; + +import { waitFor } from '@/utils'; + +interface ActionButtonProps { + clickAction: (e: SyntheticEvent) => void; + actionLabel: string; + progressLabel: string; +} + +const ActionButton: FC = (props) => { + const { clickAction, actionLabel, progressLabel } = props; + const [inProgress, setInProgress] = useState(false); + + const handleOnClick = async (e: SyntheticEvent) => { + setInProgress(true); + clickAction(e); + await waitFor(700); + setInProgress(false); + }; + + return ( + + ); +}; + +export default ActionButton; diff --git a/src/components/ActionButton/index.ts b/src/components/ActionButton/index.ts new file mode 100644 index 0000000..2904da6 --- /dev/null +++ b/src/components/ActionButton/index.ts @@ -0,0 +1 @@ +export { default } from './ActionButton'; From dd332fca46ce3ba79093c75724c1410db8ce818f Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Mon, 8 Apr 2024 16:11:06 +0930 Subject: [PATCH 03/26] PERT-48: add a way to show jira helper tasks eg: copying ticket lists * move PERT button to a container * add a task button (sparkles) which showes more task actions on hover --- src/components/PertModal/PertModal.module.css | 56 +++++++-- src/components/PertModal/PertModal.tsx | 116 +++++++++++++++--- src/utils/index.ts | 2 + src/utils/update-clipboard.ts | 17 +++ src/utils/wait-for.ts | 4 + 5 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 src/utils/update-clipboard.ts create mode 100644 src/utils/wait-for.ts diff --git a/src/components/PertModal/PertModal.module.css b/src/components/PertModal/PertModal.module.css index b6ed909..43b1462 100644 --- a/src/components/PertModal/PertModal.module.css +++ b/src/components/PertModal/PertModal.module.css @@ -36,12 +36,24 @@ 0px 0px 1px #03040480; } -.openPertModalButton { +.pertButtons { + position: fixed; + right: 20px; + bottom: 20px; + z-index: 401287331; + display: flex; + align-items: flex-end; + gap: 1rem; +} + +.pertButtons button { --to: #9061f9; --from: #e74694; - background-image: linear-gradient(to right, var(--from), var(--to)); - box-shadow: 0 10px 15px -3px rgba(231, 70, 148, 0.5), + background-image: linear-gradient(100deg, var(--from), var(--to)); + background-size: 150% 100%; + background-position: left; + box-shadow: 0 5px 10px -3px rgba(231, 70, 148, 0.5), 0 4px 6px -4px rgba(231, 70, 148, 0.5); color: var(--pert-modal-button-color); white-space: nowrap; @@ -51,14 +63,28 @@ font-size: 1rem; font-weight: bold; cursor: pointer; - position: fixed; - right: 20px; - bottom: 20px; - z-index: 401287331; + transition: all ease-in-out .3s; +} + +.jiraWithWingsTools { + display: flex; + flex-direction: column-reverse; + align-items: flex-end; + gap: 1rem; + margin: 0; + padding: 0; +} + +.jiraWithWingsTools button { + position: static; +} + +.jiraWithWingsTools:not(:hover):not(:focus-within) dd { + display: none; } -.openPertModalButton:hover { - background-image: linear-gradient(to right, var(--to), var(--from)); +.pertButtons button:hover { + background-position: right; } :global(body:not(:has([contenteditable='true'])) #pert-button-jira) { @@ -79,6 +105,18 @@ display: none; } +.copyTicketsListButton { + display: none; +} + +body:has([data-component-selector="VersionDetailIssueListIssueCardContainer"]) .copyTicketsListButton { + display: block; +} + +body:has([aria-label="Issues"]) .copyTicketsListButton { + display: block; +} + .content { color: var(--pert-color); font-size: 1rem; diff --git a/src/components/PertModal/PertModal.tsx b/src/components/PertModal/PertModal.tsx index 9906747..42a204e 100644 --- a/src/components/PertModal/PertModal.tsx +++ b/src/components/PertModal/PertModal.tsx @@ -2,6 +2,7 @@ import { CSSProperties, FC, Fragment, + SyntheticEvent, useContext, useEffect, useMemo, @@ -17,6 +18,7 @@ import { import ReactModal from 'react-modal'; import { PertContextType } from '@/@types/pertData'; +import ActionButton from '@/components/ActionButton'; import AdvancedSettings from '@/components/AdvancedSettings'; import Field from '@/components/Field'; import Header from '@/components/Header'; @@ -30,6 +32,8 @@ import { getRandomTranslation, getTicketNo, handleMouseOver, + updateClipboard, + waitFor, } from '@/utils'; import classes from './PertModal.module.css'; @@ -122,7 +126,7 @@ const PertModal: FC = () => { setIsPertModalOpen(true); }; - const handleCopy = async (e: React.SyntheticEvent) => { + const handleCopy = async (e: SyntheticEvent) => { e.preventDefault(); const form = formRef.current; @@ -135,9 +139,7 @@ const PertModal: FC = () => { if (!html) return; setCopied(true); - await new Promise((resolve) => { - setTimeout(resolve, 300); - }); + await waitFor(700); setCopied(false); const blobInput = new Blob([html.innerHTML], { type: 'text/html' }); @@ -147,6 +149,71 @@ const PertModal: FC = () => { setIsPertModalOpen(false); }; + const handleCopyTicketsForSlack = (e: SyntheticEvent) => { + e.preventDefault(); + try { + const hasSelection = window.getSelection()?.toString() !== ''; + + if (hasSelection) { + const selection = window.getSelection()?.getRangeAt(0)?.cloneContents(); + const ticketList = [ + ...(selection?.querySelectorAll( + '[data-testid="native-issue-table.ui.issue-row"]' + ) as NodeListOf), + ...(selection?.querySelectorAll( + '[data-testid="issue-navigator.ui.issue-results.detail-view.card-list.card.list-item"]' + ) as NodeListOf), + ] + .filter((a) => a.hasAttribute('data-testid')) + .map((ticket) => { + const ticketLink = + (ticket.querySelector( + '[data-component-selector="jira-native-issue-table-issue-key"]' + ) as HTMLAnchorElement) || + (ticket.querySelector( + '[data-testid="issue-navigator.ui.issue-results.detail-view.card-list.card"]' + ) as HTMLAnchorElement); + const ticketDescription = + (ticket.querySelector( + '[data-testid="issue-field-inline-edit-read-view-container.ui.container"]' + ) as HTMLDivElement) || + (ticket.querySelector( + '[data-testid="issue-navigator.ui.issue-results.detail-view.card-list.card.summary"]' + ) as HTMLDivElement); + return `
  • ${ + ticketLink.href.match(/[A-Z]{2,}-\d+/)?.[0] || '' + }: ${ticketDescription.textContent}
  • `; + }) + .join(''); + updateClipboard(ticketList); + return; + } + + const ticketList = [ + ...(document.querySelectorAll( + '[data-component-selector="VersionDetailIssueListIssueCardContainer"]' + ) as NodeListOf), + ] + .map( + (ticket) => + `
  • ${[...ticket.childNodes[0].childNodes] + .map((a) => a.textContent) + .filter(Boolean) + .join(': ')}
  • ` + ) + .join(''); + + if (!ticketList) { + alert('Highlight what you want to copy'); + return; + } + + updateClipboard(ticketList); + } catch (e) { + console.error(e); + } + }; + useEffect(() => { const ticketModalSelector = IS_JIRA ? '.atlaskit-portal-container' @@ -162,18 +229,35 @@ const PertModal: FC = () => { return ( <> - +
    +
    +
    + +
    +
    + +
    +
    + +
    { + const tempHTMLContainer = document.createElement('ul'); + tempHTMLContainer.innerHTML = content; + const blobHTML = new Blob([tempHTMLContainer.outerHTML], { + type: 'text/html', + }); + const blobText = new Blob([tempHTMLContainer.outerHTML], { + type: 'text/plain', + }); + const clipboardItemInput = new ClipboardItem({ + ['text/plain']: blobText, + ['text/html']: blobHTML, + }); + navigator.clipboard + .write([clipboardItemInput]) + .then(() => console.log('content copied to clipboard')); +}; diff --git a/src/utils/wait-for.ts b/src/utils/wait-for.ts new file mode 100644 index 0000000..de16eec --- /dev/null +++ b/src/utils/wait-for.ts @@ -0,0 +1,4 @@ +export const waitFor = (waitTime = 300) => + new Promise((resolve) => { + setTimeout(resolve, waitTime); + }); From 5ea60ae6a514ecf99ba7656e9ea290eaf6b52106 Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Mon, 8 Apr 2024 16:11:24 +0930 Subject: [PATCH 04/26] PERT-48: fix import order --- src/components/Header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 8f77ef2..9ff04da 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,6 +1,6 @@ import { FC, useContext } from 'react'; -import { MdClose, MdMinimize } from 'react-icons/md'; import { useTranslation } from 'react-i18next'; +import { MdClose, MdMinimize } from 'react-icons/md'; import { PertContextType } from '@/@types/pertData'; import Message from '@/components/Message'; From 58657ccd10327f1bb701129a51e81cdea95ee3dc Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Mon, 8 Apr 2024 16:11:47 +0930 Subject: [PATCH 05/26] PERT-48: update azure tests to match azure login flow updates --- src/__tests__/azuredevops.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__tests__/azuredevops.spec.ts b/src/__tests__/azuredevops.spec.ts index da82b46..4550448 100644 --- a/src/__tests__/azuredevops.spec.ts +++ b/src/__tests__/azuredevops.spec.ts @@ -28,11 +28,11 @@ describe('test PERT with wings extension in Azure DevOps.', async () => { timeout, }); await page.type('[name="loginfmt"]', AZUREDEVOPS_USER); - await page.waitForSelector('[data-report-event="Signin_Submit"]', { + await page.waitForSelector('[type="submit"]', { visible: true, timeout, }); - await page.click('[data-report-event="Signin_Submit"]'); + await page.click('[type="submit"]'); // password screen const a = await page.waitForSelector('#otcLoginLink', { @@ -40,18 +40,18 @@ describe('test PERT with wings extension in Azure DevOps.', async () => { timeout, }); await page.type('[name="passwd"]', AZUREDEVOPS_PASSWORD); - await page.waitForSelector('[data-report-event="Signin_Submit"]', { + await page.waitForSelector('[type="submit"]', { visible: true, timeout, }); - await page.click('[data-report-event="Signin_Submit"]'); + await page.click('[type="submit"]'); // Stay signed in screen - await page.waitForSelector('[data-report-event="Signin_Submit"]', { + await page.waitForSelector('[id="acceptButton"]', { visible: true, timeout, }); - await page.click('[data-report-event="Signin_Submit"]'); + await page.click('[id="acceptButton"]'); }); it('should render a button in Azure DevOps ticket.', async () => { From 379b73ac946c54f1540ca8f4cf240ad5d3bb4249 Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Mon, 8 Apr 2024 16:14:47 +0930 Subject: [PATCH 06/26] PERT-46: allow same value for all 3 fields * we are only checking if relevent fields are not less now. --- src/components/PertRowsForm/PertRowsForm.tsx | 4 ++-- src/components/PertTable/PertTable.tsx | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/PertRowsForm/PertRowsForm.tsx b/src/components/PertRowsForm/PertRowsForm.tsx index 7d7de70..30f403f 100644 --- a/src/components/PertRowsForm/PertRowsForm.tsx +++ b/src/components/PertRowsForm/PertRowsForm.tsx @@ -91,7 +91,7 @@ const PertRowsForm: FC = () => { return; } - if (optimisticMinutes >= likelyMinutes) { + if (optimisticMinutes > likelyMinutes) { updatePertMessage( id, 'error', @@ -100,7 +100,7 @@ const PertRowsForm: FC = () => { return; } - if (likelyMinutes >= pessimisticMinutes) { + if (likelyMinutes > pessimisticMinutes) { updatePertMessage( id, 'error', diff --git a/src/components/PertTable/PertTable.tsx b/src/components/PertTable/PertTable.tsx index 19f3168..76212d5 100644 --- a/src/components/PertTable/PertTable.tsx +++ b/src/components/PertTable/PertTable.tsx @@ -108,12 +108,20 @@ const PertTable: FC = ({ forwardRef }) => { [pertMinutes] ); - const isValidPert = - optimisticMinutes < likelyMinutes && likelyMinutes < pessimisticMinutes; + const isValidPert = useMemo(() => { + return pertRows.every( + (row) => + row.error === '' && + row.pessimistic !== '' && + row.likely !== '' && + row.optimistic + ); + }, [pertData]); + const isValidQaMinutes = qAExactMinutes && - qAExactMinutes.optimistic < qAExactMinutes.likely && - qAExactMinutes.likely < qAExactMinutes.pessimistic; + qAExactMinutes.optimistic <= qAExactMinutes.likely && + qAExactMinutes.likely <= qAExactMinutes.pessimistic; const devTasks = pertRows.filter((row) => !row.isQATask); From 15559d73617b44f2c5886a1be462e4ee269be32e Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Mon, 8 Apr 2024 16:16:53 +0930 Subject: [PATCH 07/26] release 1.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e7a2f0e..e957849 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pert-with-wings", "private": true, - "version": "1.4.1", + "version": "1.5.0", "type": "module", "scripts": { "dev": "vite", From 52b42f7c6d071f4a1ee92754f536ba01edd02545 Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Tue, 14 May 2024 16:07:29 +0930 Subject: [PATCH 08/26] PERT-49: add partysocket, partykit client package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e957849..88021b6 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "classnames": "^2.3.2", "i18next": "^22.4.15", + "partysocket": "^1.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^12.2.2", From 3fe40ea73eb108affab302a05006ad2e4a6e1095 Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Tue, 14 May 2024 16:32:01 +0930 Subject: [PATCH 09/26] PERT-49: add ability to set class and disabled props in ActionButton --- manifest.config.ts | 3 +- package.json | 7 +- src/components/ActionButton/ActionButton.tsx | 21 +- tsconfig.json | 3 +- vite.config.ts | 6 +- yarn.lock | 3363 ++++++++++-------- 6 files changed, 1865 insertions(+), 1538 deletions(-) diff --git a/manifest.config.ts b/manifest.config.ts index 2b723da..4f7f791 100644 --- a/manifest.config.ts +++ b/manifest.config.ts @@ -1,10 +1,11 @@ import { defineManifest } from '@crxjs/vite-plugin'; + import packageJson from './package.json'; const { version } = packageJson; export default defineManifest(async (env) => ({ manifest_version: 3, - name: 'PERT With Wings', + name: `PERT With Wings ${env.mode === 'development' ? ' - Dev Build' : ''}`, description: 'Helper to make PERT estimates in JIRA / Azure DevOps tickets', version, action: {}, diff --git a/package.json b/package.json index 88021b6..a06349e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pert-with-wings", "private": true, - "version": "1.5.0", + "version": "2.0.0", "type": "module", "scripts": { "dev": "vite", @@ -27,6 +27,7 @@ "devDependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.7", "@types/chrome": "^0.0.203", + "@types/dom-navigation": "^1.0.3", "@types/puppeteer": "^7.0.4", "@types/react": "^18.0.24", "@types/react-dom": "^18.0.8", @@ -34,7 +35,7 @@ "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/parser": "^5.48.2", - "@vitejs/plugin-react": "^2.2.0", + "@vitejs/plugin-react": "^4.2.1", "dotenv": "^16.0.3", "eslint": "^8.32.0", "eslint-config-prettier": "^8.6.0", @@ -46,7 +47,7 @@ "prettier": "^2.8.3", "puppeteer": "^19.5.2", "rimraf": "^4.1.1", - "typescript": "^4.6.4", + "typescript": "^5.4.5", "uuid-by-string": "^4.0.0", "vite": "^4.0.4", "vitest": "^0.28.3" diff --git a/src/components/ActionButton/ActionButton.tsx b/src/components/ActionButton/ActionButton.tsx index 3e45844..47f6429 100644 --- a/src/components/ActionButton/ActionButton.tsx +++ b/src/components/ActionButton/ActionButton.tsx @@ -6,11 +6,19 @@ import { waitFor } from '@/utils'; interface ActionButtonProps { clickAction: (e: SyntheticEvent) => void; actionLabel: string; - progressLabel: string; + progressLabel?: string; + className?: string; + disabled?: boolean; } const ActionButton: FC = (props) => { - const { clickAction, actionLabel, progressLabel } = props; + const { + clickAction, + actionLabel, + progressLabel = '', + className = '', + disabled = false, + } = props; const [inProgress, setInProgress] = useState(false); const handleOnClick = async (e: SyntheticEvent) => { @@ -21,8 +29,13 @@ const ActionButton: FC = (props) => { }; return ( - - -
    - -
    - + {IS_JIRA && ( +
    +
    + +
    +
    + +
    +
    + +
    +
    + )} + {pp && } void; +} + +const CARD_BACKGROUNDS = [ + { + background: `linear-gradient(135deg,#0000 18.75%,#47d3ff 0 31.25%,#0000 0), +repeating-linear-gradient(45deg,#47d3ff -6.25% 6.25%,#474bff 0 18.75%)`, + backgroundSize: `64px 64px`, + }, + { + background: `repeating-conic-gradient(from 45deg, #474bff 0% 25%, #47d3ff 0% 50%)`, + backgroundSize: `32px 32px`, + backgroundColor: `#47d3ff`, + }, + { + backgroundImage: `repeating-conic-gradient(from 30deg, #474bff 0% 60deg, #47d3ff 0% 120deg)`, + backgroundSize: `32px 55px`, + backgroundColor: `#47d3ff`, + }, + { + background: `conic-gradient(from 116.56deg at calc(100%/3) 0, #0000 90deg,#47d3ff 0), + conic-gradient(from -63.44deg at calc(200%/3) 100%, #0000 90deg,#47d3ff 0) +#474bff`, + backgroundSize: `32px 32px`, + }, + { + background: `linear-gradient(135deg, #474bff 25%, transparent 25%) -32px 0, linear-gradient(225deg, #474bff 25%, transparent 25%) -32px 0, linear-gradient(315deg, #474bff 25%, transparent 25%), linear-gradient(45deg, #474bff 25%, transparent 25%)`, + backgroundSize: `64px 64px`, + backgroundColor: `#47d3ff`, + }, +]; + +const CARDS = [1, 2, 3, 5, 8, 13, 21, '☕'] as const; + +const PlanningPoker: FC = (props) => { + const { exit } = props; + const currentUser = getUserData(); + const [party, setParty] = useState([]); + const [socketConnected, setSocketConnected] = useState(false); + const [showCards, setShowCards] = useState(false); + const [cardsRevealed, setCardsRevealed] = useState(false); + const ws = usePartySocket({ + host: + import.meta.env.MODE === 'development' + ? 'localhost:1999' + : 'https://pww.thilinaaligent.partykit.dev', // or localhost:1999 in dev + room: getTicketNo(), + + onOpen() { + setSocketConnected(true); + ws.send( + JSON.stringify({ + type: 'add-user', + payload: { + ...currentUser, + score: null, + }, + }) + ); + }, + onMessage(e) { + const data = safelyParseJson(e.data); + // console.log(JSON.stringify(e.data)); + if (data.type === 'presence') { + setParty(data.payload.users.filter(Boolean)); + } + + if (data.type === 'reveal-cards') { + setCardsRevealed(true); + } + }, + onClose() { + console.log('closed'); + setSocketConnected(false); + }, + onError() { + console.log('error'); + setSocketConnected(false); + }, + }); + + const handleShowChoices = () => { + ws.send( + JSON.stringify({ + type: 'set-score', + payload: { + ...currentUser, + score: null, + }, + }) + ); + setShowCards(true); + }; + + const handleChooseCard = (card: number | string) => { + ws.send( + JSON.stringify({ + type: 'set-score', + payload: { + ...currentUser, + score: card, + }, + }) + ); + setShowCards(false); + }; + + const handleRevealCards = () => { + ws.send( + JSON.stringify({ + type: 'reveal-cards', + }) + ); + setCardsRevealed(true); + }; + + // const message = ; + // const messageJson = message ? JSON.stringify(message) : null; + + const uniqueParty = useMemo(() => { + return [...new Map(party.map((item) => [item['name'], item])).values()]; + }, [party]); + + const canRevealCards = useMemo(() => { + return uniqueParty.length > 1 && uniqueParty.every((user) => user.score); + }, [uniqueParty]); + + const canChooseCards = useMemo(() => { + return uniqueParty.length > 1; + }, [uniqueParty]); + + useEffect(() => { + if (!currentUser) return; + console.log(currentUser, getTicketNo(), 'starting'); + }, [ws]); + + useEffect(() => { + const listenNavigate = () => { + ws.updateProperties({ + room: getTicketNo(), + }); + ws.reconnect(); + }; + + //@ts-expect-error navigation api not supported yet + navigation.addEventListener('navigate', listenNavigate); + + return () => { + //@ts-expect-error navigation api not supported yet + navigation.removeEventListener('navigate', listenNavigate); + }; + }, []); + + if (!socketConnected) return null; + + return ( +
    +
      + {uniqueParty.map(({ name, avatar, score }, index) => ( +
    • + + {currentUser.name === name && ( + <> + +
        + {CARDS.map((card, index) => ( +
      • + +
      • + ))} +
      + + )} +
    • + ))} +
    • + +
    • +
    +
    + ); +}; + +export default PlanningPoker; diff --git a/src/components/PlanningPoker/index.ts b/src/components/PlanningPoker/index.ts new file mode 100644 index 0000000..34afaf8 --- /dev/null +++ b/src/components/PlanningPoker/index.ts @@ -0,0 +1 @@ +export { default } from './PlanningPoker'; diff --git a/src/utils/get-user-data.ts b/src/utils/get-user-data.ts new file mode 100644 index 0000000..94fea16 --- /dev/null +++ b/src/utils/get-user-data.ts @@ -0,0 +1,16 @@ +export const getUserData = () => { + return { + name: + ( + document.querySelector( + '[name="ajs-remote-user-fullname"]' + ) as HTMLMetaElement + )?.content || '', + avatar: + ( + document.querySelector( + '[data-test-id="ak-spotlight-target-profile-spotlight"] img' + ) as HTMLImageElement + )?.src || '', + }; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 0dbae4d..c1fcca8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -8,6 +8,8 @@ export * from './get-random-translation'; export * from './get-sums'; export * from './get-ticket-no'; export * from './get-time-string'; +export * from './get-user-data'; export * from './save-config'; +export * from './safely-parse-json'; export * from './wait-for'; export * from './update-clipboard'; diff --git a/src/utils/safely-parse-json.ts b/src/utils/safely-parse-json.ts new file mode 100644 index 0000000..e178a3d --- /dev/null +++ b/src/utils/safely-parse-json.ts @@ -0,0 +1,11 @@ +export const safelyParseJson = (maybeJson: string) => { + let parsed; + + try { + parsed = JSON.parse(maybeJson); + } catch (e) { + parsed = maybeJson; + } + + return parsed; +}; From ee8e6599be54a5fbeccf76d8cbdc415f51df28de Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Tue, 14 May 2024 16:44:24 +0930 Subject: [PATCH 11/26] PERT-49: rename state variable for readability --- src/components/PertModal/PertModal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/PertModal/PertModal.tsx b/src/components/PertModal/PertModal.tsx index 0020e7e..b581903 100644 --- a/src/components/PertModal/PertModal.tsx +++ b/src/components/PertModal/PertModal.tsx @@ -62,7 +62,7 @@ const PertModal: FC = () => { const formRef = useRef(null); const pertHtmlRef = useRef(null); const [copied, setCopied] = useState(false); - const [pp, setPp] = useState(false); + const [showPlanningPoker, setShowPlanningPoker] = useState(false); const { pertData, setIsPertModalOpen, isPertModalOpen, setTicketNo } = useContext(PertContext) as PertContextType; @@ -217,7 +217,7 @@ const PertModal: FC = () => { }; const handlePlanningPoker = () => { - setPp(true); + setShowPlanningPoker(true); }; useEffect(() => { @@ -270,7 +270,7 @@ const PertModal: FC = () => { )} - {pp && } + {showPlanningPoker && } Date: Wed, 15 May 2024 14:57:59 +0930 Subject: [PATCH 12/26] PERT-49: show buttons based on jira page / state --- src/components/PertModal/PertModal.module.css | 55 ++++++++++++++----- src/components/PertModal/PertModal.tsx | 19 ++++--- .../PlanningPoker/PlanningPoker.module.css | 19 ++++++- 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/components/PertModal/PertModal.module.css b/src/components/PertModal/PertModal.module.css index 9555983..d0d3b73 100644 --- a/src/components/PertModal/PertModal.module.css +++ b/src/components/PertModal/PertModal.module.css @@ -80,20 +80,30 @@ opacity: 0; transition: all 0.3s ease-in-out; z-index: 0; + width: 0; + height: 0; + pointer-events: none; +} + +.pert:has(.planningPokerContainer) .pertButtons { + display: none; } .pertButtons:hover .jiraWithWingsTools { left: 0; opacity: 1; + width: auto; + height: auto; + pointer-events: auto; } .jiraWithWingsTools button { position: static; } -.jiraWithWingsTools:not(:hover) dd { - display: none; -} +/*.jiraWithWingsTools:not(:hover) dd {*/ +/* display: none;*/ +/*}*/ .pertButtons button:hover { background-position: right; @@ -103,7 +113,16 @@ display: none; } -:global(body:has(#jira-issue-header) #pert-button-jira) { +.planningPokerButton { + display: block; +} + +:global(body:not(:has([contenteditable='true']))) .planningPokerButton { + display: none; +} + +:global(body:has(#jira-issue-header) #pert-button-jira), +:global(body:has(#jira-issue-header)) .planningPokerButton { display: block; } @@ -112,22 +131,30 @@ display: none; } -:global(body#com-atlassian-confluence #pert-button-jira) { +:global(body#com-atlassian-confluence .pert) { /* hide in confluence pages */ display: none; } -/*.copyTicketsListButton {*/ -/* display: none;*/ -/*}*/ +.copyTicketsListButton { + display: none; +} -/*body:has([data-component-selector="VersionDetailIssueListIssueCardContainer"]) .jiraWithWingsTools {*/ -/* display: block;*/ -/*}*/ +/* show copy tickets button when in release page / issue list page if ticket preview is not visible */ +body:has([data-component-selector="VersionDetailIssueListIssueCardContainer"]):not(:has([data-testid="issue.views.issue-details.issue-layout.issue-layout"])) .jiraWithWingsTools, +body:has([aria-label="Issues"]):not(:has([data-testid="issue.views.issue-details.issue-layout.issue-layout"])) .jiraWithWingsTools { + display: block; + left: 0; + opacity: 1; + width: auto; + height: auto; +} -/*body:has([aria-label="Issues"]) .jiraWithWingsTools {*/ -/* display: block;*/ -/*}*/ + +body:has([aria-label="Issues"]) .copyTicketsListButton, +body:has([data-component-selector="VersionDetailIssueListIssueCardContainer"]) .copyTicketsListButton { + display: block; +} .content { color: var(--pert-color); diff --git a/src/components/PertModal/PertModal.tsx b/src/components/PertModal/PertModal.tsx index b581903..a0af0d4 100644 --- a/src/components/PertModal/PertModal.tsx +++ b/src/components/PertModal/PertModal.tsx @@ -234,24 +234,21 @@ const PertModal: FC = () => { }, [isPertModalOpen]); return ( - <> +
    {IS_JIRA && (
    -
    - -
    -
    +
    @@ -270,7 +267,11 @@ const PertModal: FC = () => { )}
    - {showPlanningPoker && } + {showPlanningPoker && ( +
    + +
    + )} {
    - + ); }; diff --git a/src/components/PlanningPoker/PlanningPoker.module.css b/src/components/PlanningPoker/PlanningPoker.module.css index c68e2d4..58b8307 100644 --- a/src/components/PlanningPoker/PlanningPoker.module.css +++ b/src/components/PlanningPoker/PlanningPoker.module.css @@ -3,7 +3,7 @@ position: fixed; right: 20px; - bottom: 70px; + bottom: 20px; width: var(--card-size); z-index: 401287331; } @@ -171,6 +171,7 @@ } .revealButton { + white-space: nowrap; border: 0; border-radius: 0.25rem; padding: 0.5rem 0.75rem; @@ -179,3 +180,19 @@ cursor: pointer; width: 100%; } + +.revealButton:not(:disabled) { + --to: #9061f9; + --from: #e74694; + + background-image: linear-gradient(100deg, var(--from), var(--to)); + background-size: 150% 100%; + background-position: left; + box-shadow: 0 5px 10px -3px rgba(231, 70, 148, 0.5), + 0 4px 6px -4px rgba(231, 70, 148, 0.5); + color: var(--pert-modal-button-color); +} + +.revealButton:hover { + background-position: right; +} From 147de51d8525f3256a00c8f49462c31a46c046ed Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Thu, 1 Aug 2024 15:38:42 +0930 Subject: [PATCH 13/26] fix codestyle --- src/components/PertModal/PertModal.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PertModal/PertModal.module.css b/src/components/PertModal/PertModal.module.css index d0d3b73..63a0946 100644 --- a/src/components/PertModal/PertModal.module.css +++ b/src/components/PertModal/PertModal.module.css @@ -63,7 +63,7 @@ font-size: 1rem; font-weight: bold; cursor: pointer; - transition: all ease-in-out .3s; + transition: all ease-in-out 0.3s; position: relative; z-index: 1; } From aae76e94131f5fb082eb8fbd7ac9933cf5f62ff0 Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Thu, 1 Aug 2024 15:39:16 +0930 Subject: [PATCH 14/26] add ability to render a rich node as button label --- src/components/ActionButton/ActionButton.tsx | 6 +++--- src/components/PertModal/PertModal.module.css | 7 +++++++ src/components/PertModal/PertModal.tsx | 8 ++++++-- src/components/PlanningPoker/PlanningPoker.tsx | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/components/ActionButton/ActionButton.tsx b/src/components/ActionButton/ActionButton.tsx index 47f6429..06ee3ad 100644 --- a/src/components/ActionButton/ActionButton.tsx +++ b/src/components/ActionButton/ActionButton.tsx @@ -1,11 +1,11 @@ -import { FC, Fragment, SyntheticEvent, useState } from 'react'; +import { FC, Fragment, ReactNode, SyntheticEvent, useState } from 'react'; import { MdCheckCircle } from 'react-icons/md'; import { waitFor } from '@/utils'; interface ActionButtonProps { clickAction: (e: SyntheticEvent) => void; - actionLabel: string; + actionLabel: ReactNode; progressLabel?: string; className?: string; disabled?: boolean; @@ -40,7 +40,7 @@ const ActionButton: FC = (props) => { {progressLabel} ) : ( - actionLabel + {actionLabel} )} ); diff --git a/src/components/PertModal/PertModal.module.css b/src/components/PertModal/PertModal.module.css index 63a0946..4ecc3c3 100644 --- a/src/components/PertModal/PertModal.module.css +++ b/src/components/PertModal/PertModal.module.css @@ -68,6 +68,13 @@ z-index: 1; } +.pertButtons button sup { + color: var(--pert-modal-button-color); + text-transform: uppercase; + font-weight: bold; + font-size: 9px; +} + .jiraWithWingsTools { display: flex; flex-direction: column-reverse; diff --git a/src/components/PertModal/PertModal.tsx b/src/components/PertModal/PertModal.tsx index a0af0d4..d86b9f7 100644 --- a/src/components/PertModal/PertModal.tsx +++ b/src/components/PertModal/PertModal.tsx @@ -241,14 +241,18 @@ const PertModal: FC = () => {
    ✨ Copy tickets list for Slack} progressLabel="Copied" />
    + ✨ Planning Poker BETA + + } />
    diff --git a/src/components/PlanningPoker/PlanningPoker.tsx b/src/components/PlanningPoker/PlanningPoker.tsx index 858043a..3ba0702 100644 --- a/src/components/PlanningPoker/PlanningPoker.tsx +++ b/src/components/PlanningPoker/PlanningPoker.tsx @@ -264,7 +264,7 @@ const PlanningPoker: FC = (props) => { className={classes.revealButton} disabled={!canRevealCards || cardsRevealed} clickAction={handleRevealCards} - actionLabel="Reveal" + actionLabel={<>Reveal} /> From 3a1572b937b179a651f6b01f719812fc1737021d Mon Sep 17 00:00:00 2001 From: Thilina Gunasekara Date: Thu, 1 Aug 2024 15:56:16 +0930 Subject: [PATCH 15/26] add beta label to copy tickets --- src/components/PertModal/PertModal.tsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/PertModal/PertModal.tsx b/src/components/PertModal/PertModal.tsx index e010bd4..d8c3f1e 100644 --- a/src/components/PertModal/PertModal.tsx +++ b/src/components/PertModal/PertModal.tsx @@ -243,7 +243,11 @@ const PertModal: FC = () => {
    ✨ Copy tickets list for Slack} + actionLabel={ + <> + ✨ Copy tickets list for Slack BETA + + } progressLabel="Copied" />
    @@ -260,13 +264,13 @@ const PertModal: FC = () => { )}