ããã«ã¡ã¯ãXI æ¬éšãœãããŠã§ã¢ãã¶ã€ã³ã»ã³ã¿ãŒæå±ã»æ°å 1 幎ç®ã®æŸæ¬ã§ãã æšå¹Ž10æã«é
å±ãããŠãã 3 ã¶æéãAtlassian Forge ã䜿ã£ã ã¢ããªéçº ãæ
åœããŸããã ããã§ä»åã¯ãForge ã®æŠèŠãšå®éã®éçºæé ã解説ããŠãããŸãã Forge ã«é¢ããæ¥æ¬èªèšäºã¯ã»ãšãã©ãªãå¿çŽ°ãæ³ããããã®ã§ãæ¬èšäºã誰ãã®å©ãã«ãªãã°å¹žãã§ãã Forge ãšã¯ UI æ§ç¯ææ³ 2 ãã¿ãŒã³ UI kit Custom UI éçºæé ã®äŸ å°éç®æš å·çè
ã®ç°å¢ â Node.js ã®å°å
¥ â¡ Forge CLI ã®å°å
¥ ⢠ãããžã§ã¯ãã®äœæ ⣠ãã«ãã»ãããã€ã»ã€ã³ã¹ããŒã« †TypeScript ãžã®å¯Ÿå¿ ⥠Atlassian REST API ãš UI kit ããã¯ã䜿ããå¿
èŠãªæ
å ±ãååŸããŠã¿ã ⊠ååŸããå€ã衚瀺ãã â§ ç·šéå
容ãæåºã§ããããã«ãã éçºã䟿å©ã«ãã Tips UI kit ã䜿ã£ãŠã¿ãææ³ ãããã« Forge ãšã¯ Atlassian 瀟ãæäŸãã FaaS ãã©ãããã©ãŒã ã§ããAtlassian ã®ãµãŒãã¹ãã«ã¹ã¿ãã€ãºã»æ©èœæ¡åŒµããããã®ã¢ããªãããŠãŒã¶ãŒèªèº«ã§ç°¡åã«äœæã§ããŸããçŸåšã¯ãJiraãJira Service ManagementãConfluence ã®3ã€ã®ãµãŒãã¹ã«å¯Ÿå¿ããŠãããäœæããã¢ããªã¯ãããã®ãµãŒãã¹äžããå©çšããããšãšãªããŸãã ã¢ããªã®äŸãšããŠã¯ã Jira ã®èª²é¡ããã«ããå€èšèªç¿»èš³ã䜿ããããã«ããã¢ã㪠Jira ã®èª²é¡ã®å¥å
šæ§ããæŽæ°ã®æ»ããªã©ã®æ
å ±ãããšã«å€æããŠãããã¢ã㪠Confluence ã«ã Google ãã©ãã«ä¿åãããŠããåçã衚瀺ã§ããããã«ããã¢ã㪠ãªã©ãå
¬åŒããã¥ã¡ã³ãã§ç޹ä»ãããŠããŸãã https://developer.atlassian.com/platform/forge/example-apps/ ã¢ããªéçº ã«äŒŽãæ©èœå®è£
以å€ã®äœæ¥ïŒãã«ãã»ãããã€ã»æš©éã®ç®¡çã»ã¹ã±ãŒãªã³ã°ã»ããã³ã管çãªã©ãªã©ïŒã¯ Atlassian åŽã§ã»ãšãã©æ
ã£ãŠããããããéçºè
ã¯å®çŸãããæ©èœã®å®è£
ã«æ³šåã§ããŸãã UI æ§ç¯ææ³ 2 ãã¿ãŒã³ ã¢ããªã® UI æ§ç¯ææ³ã 2 ãã¿ãŒã³çšæãããŠããããããããéžã¶å¿
èŠããããŸãã è¿
éã»ç°¡äŸ¿ãª UI kit ãš èªç±åºŠãé«ã Custom UI ã§ãã UI kit æäŸããã ã³ã³ããŒãã³ã ãçµã¿åãã㊠UI ãæ§ç¯ããŸãã ã³ã³ããŒãã³ã ã¯ãåçš®å
¥åãã©ãŒã ã»ãã¿ã³ã»ããŒãã«ãªã©ãè±å¯ã«çšæãããŠããŸãããããã ã³ã³ããŒãã³ã ã Atlassian 颚ã®ãã¶ã€ã³ãšãªã£ãŠãããèªåã§ãã¶ã€ã³ãèããæéãçããŸãïŒéã«ãã«ã¹ã¿ãã€ãºæ§ã¯ã»ãšãã©ãããŸããïŒã äœ¿ãæ¹ã¯ React ã® ã³ã³ããŒãã³ã ãšãã䌌ãŠããŸãããŸããããã¯æ©èœãæäŸãããŠããããã¡ããã»ãŒ React ã®ããã§ãã ãªãã ã¬ã³ããªã³ã° ã¯å
šãŠãµãŒããŒåŽã§è¡ããããããçžå¿ã®é
å»¶ãçºçããŸãããŸããçŸç¶ã ã³ã³ããŒãã³ã ãšããã¯ã¯æäŸããããã®ä»¥å€ã«ã¯äœ¿ãããšãã§ããªããããæè»æ§ã«æ¬ ããŸãïŒForge éçºããŒã ã®ååãèŠãŠãããšãè¿ã
ã§ããããã«ãªãããïŒïŒã Custom UI HTMLã» CSS ã» JavaScript ãªã©ã®éçãªãœãŒã¹ã䜿çšããŠãç¬èªã® UI ãæ§ç¯ããŸããUI kit ãšã¯ç°ãªãã ã¬ã³ããªã³ã° ã¯ãŠãŒã¶ãŒåŽã§è¡ããããããéãã§ãïŒãã ããå€éšãªãœãŒã¹ãžã®ã¢ã¯ã»ã¹ã¯ããã¯ãšã³ããçµç±ããå¿
èŠãããããšãã£ãã«ãŒã«ããããŸãïŒã ã¿ã€ãã«ã®éãã以éã®éçºæé ã§ã¯åè
ã® UI kit ãçšããŸããCustom UI ã®è§£èª¬ã¯ãŸãã®æ©äŒã«âŠã éçºæé ã®äŸ å°éç®æš æé ã®ç޹ä»ã«å
¥ãåã«ãæ¬èšäºã«ãããéçºã®å°éç®æšãæç¢ºã«ããŠãããŸãã ããã§ã¯ãJira Service Management ã«ããªã¯ ãšã¹ ãå
容ããªã¯ ãšã¹ ã¿ãŒèªèº«ãç·šéã§ããæ©èœãã远å ããããšãç®æšãšããŸãã Jira Service Management ãšã¯ãJira ãæ¡åŒµãããµãŒãã¹ãã¹ã¯ç®¡çããŒã«ã§ããJira Service Management ã§ã¯çŸç¶ãå±ãããªã¯ ãšã¹ ãïŒ= å°ã£ãŠãã人ããã®åãåããïŒã®å
容ã管çè
åŽããç·šéããããšã¯ã§ããŠãããªã¯ ãšã¹ ã¿ãŒåŽããã¯ã§ããªããšããå¶éããããŸããä»åå®è£
ããæ©èœã¯ãããããå¶éã解決ãããã®ã§ãã ãªããJira ã Confluence ã察象ã«ããå Žåãåããããªæµãã§éçºãé²ããããŸãã 宿ã€ã¡ãŒãž èšçœ®ãããã¿ã³ãæŒããšã ç·šéçšã®ã¢ãŒãã«ãéãã ç·šéã㊠submit ãæŒããšã åæ ãããã å·çè
ã®ç°å¢ äž»èŠãªãã®ã ããããŠãããŸãã macOS Monterey 12.6 Visual Studio Code 1.74.3 node 16.17.1 Forge CLI 6.4.0 Forge API 2.7.0 React 18.2.0 ããã§ã¯éçºã«å
¥ã£ãŠãããŸãããã â Node.js ã®å°å
¥ v14 以éã® LTS release ãå¿
èŠã§ããå
¥ã£ãŠããªãå Žåã¯ã€ã³ã¹ããŒã«ããŠãã ããã â¡ Forge CLI ã®å°å
¥ Forge CLI ã¯ãForge ã¢ããªã管çããããã«äœ¿çšããèŠã®ããã±ãŒãžã§ããnpm ããã€ã³ã¹ããŒã«ããŠãã ããã npm install --save-dev @forge/cli ã€ã³ã¹ããŒã«åŸã API ããŒã¯ ã³ã䜿çšããŠãã°ã€ã³ããå¿
èŠããããŸãã è©³çŽ°ãªæé ã¯ã以äžã®ããã¥ã¡ã³ããåç
§ããŠãã ããã https://developer.atlassian.com/platform/forge/getting-started/#log-in-with-an-atlassian-api-token ⢠ãããžã§ã¯ãã®äœæ 以äžã®ã³ãã³ãã§ãããžã§ã¯ããäœæããŸãã forge create ã³ãã³ããå®è¡ãããš 3 ã€è³ªåãããã®ã§ãé ã«çããŠãããŸãã (1) ã¢ããªå 奜ããªã¢ããªåãã€ããŸãããã (2) UIããŒã« UI kit ãš Custom UI ã®éžæã§ããåè¿°ã®éããä»å㯠UI kit ãéžæããŸãã (3) ã¢ãžã¥ãŒã«ã®ãã³ãã¬ãŒã Forge ã§ã¯ãJira ãªã©ã®ãµãŒãã¹ã«ã¢ããªãçµã¿èŸŒãããã®æ©èœããã¢ãžã¥ãŒã«ããšããŠæäŸããŠããŸããäœãããã¢ããªã«é©ããã¢ãžã¥ãŒã«ãéžæãããšããã®ã¢ãžã¥ãŒã«ã«åãããŠè¯ãæãã®ãã³ãã¬ãŒããäœæããŠãããŸãã ä»åã¯ãJira Service Management ã®ãªã¯ ãšã¹ ãé²èЧç»é¢ã«ç·šéãã¿ã³ãä»ãããã®ã§ããjira-service-management- portal -request-view-actionããšããã¢ãžã¥ãŒã«ãéžæããŸããããã¯ããªã¯ ãšã¹ ãé²èЧç»é¢ã«ãã¿ã³ã远å ããããã¿ã³ãã¯ãªãã¯ãããšå®çŸ©ããã¢ã¯ã·ã§ã³ãèµ°ãããããšãã§ããããšããã¢ãžã¥ãŒã«ã§ãã ã¢ãžã¥ãŒã«äžèЧã¯ä»¥äžãã確èªã§ããŸãã®ã§ãäœãããã¢ããªã«åãããŠéžãã§ã¿ãŠãã ããã https://developer.atlassian.com/platform/forge/manifest-reference/modules/ 以äž3ã€ã®éžæãå®äºãããšãèªåçã«ä»¥äžã®ãããªãããžã§ã¯ããäœæãããŸãã ã³ãŒããå°ãèŠããŠã¿ãŸãããã // src/index.jsx import ForgeUI, { render, Text, PortalRequestViewAction, ModalDialog, useState } from '@forge/ui'; const App = () => { const [isOpen, setOpen] = useState(true); if (!isOpen) { return null; } return ( <ModalDialog header="Hello" onClose={() => setOpen(false)}> <Text>Hello world!</Text> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App/> </PortalRequestViewAction> ); React ãã£ããã§ãããç°¡åã«ã³ãŒãã®è§£èª¬ãããŠãããšâŠ useState 㯠UI kit ãæäŸããããã¯ã®1ã€ã§ãäœ¿ãæ¹ã¯ React ã® useState ãšã»ãšãã©åãã§ãã <ModalDialog> ã <Text> 㯠UI kit componets ãšåŒã°ããAtlssian 颚ãã¶ã€ã³ã®ããŒããæäŸããããã® ã³ã³ããŒãã³ã ã§ãã äžæ¹ã<PortalRequestViewAction> 㯠Function components ãšåŒã°ããã¢ããªäœææã«éžæããã¢ãžã¥ãŒã«ãæäŸãã ã³ã³ããŒãã³ã ã§ããä»åã®äŸã§ã¯ã<PortalRequestViewAction> ã«å²ãŸãã UI kit componets ãããªã¯ ãšã¹ ãé²èЧããŒãžã®ãã¿ã³ãã¯ãªãã¯ããéã«è¡šç€ºãããããšãšãªããŸãã ⣠ãã«ãã»ãããã€ã»ã€ã³ã¹ããŒã« ããã§ 1 床ã¢ããªããããã€ããAtlassian ãµãŒãã¹äžã§ç¢ºèªããŠã¿ãŸãããã ã¢ããªã®ã«ãŒãã§ä»¥äžã®ã³ãã³ããå®è¡ããŸãããã 1 ã€ã§ãšã©ãŒãã§ãã¯ã»ãã«ãã»ãããã€ãŸã§ãè¡ã£ãŠããã匷åãªã³ãã³ãã§ãã forge deploy ãããã€å
㯠developãstagingãproduction ã®3ã€ãçšæãããŠããã--environment (-e) ãªãã·ã§ã³ã§æå®ã§ããŸã (ããã©ã«ã㯠development)ãä»å㯠development ã«ããŸãã ç¶ããŠã以äžã®ã³ãã³ããå®è¡ãããããã€ããã¢ããªãèªåã管çããããã³ãã«ã€ã³ã¹ããŒã«ããŸããå®è¡åŸã«ããã³ãã® ãã¡ã€ã³ ãèãããã®ã§å
¥åããŠãã ããã forge install ããã§ãAtlassianã®ãµãŒãã¹ããã¢ããªã䜿ããããã«ãªããŸããã確èªããŠã¿ãŸãããã ä»å㯠Jira Service Management ã®ãªã¯ ãšã¹ ãé²èЧç»é¢ã«ãã¿ã³ã远å ããã¢ãžã¥ãŒã«ã䜿ã£ãã®ã§ãåœè©²ç»é¢ãèŠã«è¡ããŸãã ãããšã以äžã®ããã«ãã¿ã³ãèšçœ®ãããŠããã ã¯ãªãã¯ãããšã¢ãŒãã«ãéããŠã Hello World !ããšè¡šç€ºãããŸãã ãããŸã§ 1 ããªãã³ãŒãã£ã³ã°ãããŠããŸãããçŽ æŽãããã§ããã †TypeScript ãžã®å¯Ÿå¿ æ¬ã¹ãããã¯ä»»æã§ãããUI kit ã§ã¯ç°¡åã«å¯Ÿå¿ã§ããã®ã§ããã²ãã£ãŠãããŸãããã 以äžã® 4 ã€ã®äœæ¥ãè¡ããŸãã (1) tsconfig. json ã®äœæ ä»åã¯ä»¥äžã®ããã«èšå®ããŸããã // tsconfig.json { "compilerOptions": { "target": "es2020", "jsx": "react", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true } } (2) ãœãŒã¹ãã¡ã€ã«ã®æ¡åŒµåãå€æŽ js(x) â ts(x) (3) TypeScript ã® ããªããã»ããµ ã package. json ã«è¿œå å¿
èŠãª ããªããã»ããµ ãããã°è¿œå ããŸãããã ã€ãã§ã«ãReact ããªããš index. tsx ã§ ãšã©ãŒã衚瀺ãããŠããŸãã®ã§ãreact ããã³å¯Ÿå¿ãã ããªããã»ããµ ã npm ããã€ã³ã¹ããŒã«ãããšãšãã«ãindex. tsx ã§ã€ã³ããŒãããŠãããŸãã // src/index.tsx import React from "react" ãªããTypeScript ã® ã³ã³ãã€ã« ã¯ãåè¿°ãã forge deploy ã³ãã³ãå®è¡æã«äžç·ã«å®è¡ããŠãããŸãã ⥠Atlassian REST API ãš UI kit ããã¯ã䜿ããå¿
èŠãªæ
å ±ãååŸããŠã¿ã ç¶ããŠãç·šéã«å¿
èŠãªæ
å ±ãååŸããŠãã¢ãŒãã«ã«è¡šç€ºããŠã¿ãŸãã ã¢ããªãš Atlassian ãµãŒãã¹ãšã®ããåãã¯ãåºæ¬çã«ã¯ Atlassian ãæäŸãã REST API ãä»ããŠè¡ãããŸããå ããŠãçŸåšé²èЧããŠãããªã¯ ãšã¹ ãã® ID ãªã©ãUI kit ã®ããã¯ãä»ããŠååŸããæ
å ±ãäžéšãããŸãã Forgeã§ã¯ã REST API ã®èªå¯ã¯ OAuth2.0 ã§è¡ãããŠãããæš©éãç§»è²ãããã¢ããªããŠãŒã¶ãŒã«ä»£ãã£ãŠ API åŒã³åºããè¡ããŸãããã®ãããéçºè
㯠ããŒã¯ ã³ãªã©ã®æ©å¯æ
å ±ã管çããå¿
èŠããªããç°¡æœãªã³ãŒãã§ API ãåŒã³åºããŸãã 䜵ããŠãã¢ããªã«ã©ã®çšåºŠã®æš©éïŒã¹ã³ãŒãïŒãäžããããã ãããã§ã¹ã ãã¡ã€ã« (manifest.yml) ã«èšè¿°ããå¿
èŠãããããã®ã¹ã³ãŒãã«ãã£ãŠæ±ãã API ã®ç¯å²ã決ãŸããŸãã å©çšã§ãã Atlassian REST API ã¯ä»¥äžãã確èªããŠãã ããããã® API ã«å¿
èŠãªã¹ã³ãŒããªã©ã®æ
å ±ãèšèŒãããŠããŸãã https://developer.atlassian.com/platform/forge/product-rest-api-reference/ ä»åã¯ãçŸåšé²èЧããŠãããªã¯ ãšã¹ ãã® ID ãUI kit ã® ãã㯠ã§ååŸãããã® ID ãããšã«ããªã¯ ãšã¹ ãã®èšåæãåãåããå
容ã REST API ã§ååŸããŠã¿ãŸãã çŸåšé²èЧããŠããããŒãžã®æ
å ±ã¯ãuseProductContext ãšãã UI kit ããã¯ã§ååŸããŸãããªããæ¬ããã¯ã®è¿ãå€ã®åãšã㊠ProductContext åãæäŸãããŠããŸãããJira ã®ã¿ã«å¯Ÿå¿ããŠããŠãJira Service Management ã«ã¯å¯Ÿå¿ããŠããŸãããã§ãã®ã§ãä»å㯠ProductContextForJsm åãèªèº«ã§å®çŸ©ããŠããŸãã // src/index.tsx // ProductContextForJsm ã®åå®çŸ© (çç¥ããã³ãŒãå
šæãèŠãããã確èªã§ããŸãã) const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; 次ã«ã欲ããæ
å ±ã REST API ã§ååŸããŸãããŸã㯠@forge/ api ã¢ãžã¥ãŒã«ã npm ããã€ã³ã¹ããŒã«ãããšãšãã«ãindex. tsx ã§ã€ã³ããŒãããŸãã // src/index.tsx import api, { Route, route } from "@forge/api"; ãªã¯ ãšã¹ ãæ
å ±ã®ååŸã«ã¯ãGet customer request by id or keyããšãã API ãçšããŸãããã® API ãåŒã³ã ã颿°ãå®çŸ©ããŸãããã // src/index.tsx // ResponseJson ã®åå®çŸ© (çç¥ããã³ãŒãå
šæãèŠãããã確èªã§ããŸãã) const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; API åŒã³åºãã®é¢æ°ã¯ãuseEffect å
ã§åŒã³åºããè¿ãå€ã¯ state ã«ä¿æããã®ãè¯ãã§ãããã state ã远å ãã // src/index.tsx const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect å
ã§ API ãåŒã³åºããŠãè¿ãå€ã state ã«ã»ããããŸãã // src/index.tsx useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); æåŸã«ã䜿çšãã API ãåŒã³ã ãããã«å¿
èŠãªã¹ã³ãŒããã ãããã§ã¹ã ãã¡ã€ã«ã«è¿œå ããŸãã // manifest.yml permissions: scopes: - "read:servicedesk-request" - "read:jira-work" ã³ãŒãå
šæãèŠã // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, Text, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; if (!isOpen) { return null; } return ( <ModalDialog header="Hello" onClose={() => setOpen(false)}> <Text>Hello world!</Text> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); ⊠ååŸããå€ã衚瀺ãã ç¶ããŠãå
ã»ã©ååŸããå€ãç»é¢ã«è¡šç€ºããŠã¿ãŸããããJira Service Management ã§ã¯å€æ§ãªèšåã¿ã€ãïŒããã¹ãããã¯ã¹ã ãã§ãã¯ããã¯ã¹ ã ã©ãžãªãã¿ã³ ã»ã»ã»ïŒããããŸããããããå
šãŠã«å¯Ÿå¿ããããšãããšãæ¬èšäºã§ã¯åãŸããããŸãããä»åã¯ç°¡æçã«ããªã¯ ãšã¹ ãã®ãèŠçŽãæ¬ã®ã¿ã衚瀺ããŠã¿ãããšã«ããŸãã ããã§ãã FormãTextField ãšãã UI kit ã³ã³ããŒãã³ã ã䜿çšããã®ã§ããŸãã¯ã€ã³ããŒãã远å ããŸããText ã³ã³ããŒãã³ã ã¯ãã䜿ããªãã®ã§ãæ¶ããŠãããŸãã // src/index.tsx import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; ç¶ããŠã REST API ã§åãåã£ããªã¯ ãšã¹ ãã®æ
å ±ããèŠçŽæ¬ã®æ
å ±ã ããæãåºãã TextField ã³ã³ããŒãã³ã ã«å€æãã颿°ãå®çŸ©ããŸãã // src/index.tsx const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; æåŸã«ãå
ã»ã©äœæãã颿°ããè¿ããã TextField ã³ã³ããŒãã³ã ã Form ã³ã³ããŒãã³ã ã§å²ã¿ãApp 颿°ã§è¿ãããã«ããŸããçŸåšã Hello World ããè¿ããŠããéšåã倿ŽããŸãã // src/index.tsx return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={() => setOpen(false)}>{makeTextField(responseJson)}</Form> </ModalDialog> ); Form ã³ã³ããŒãã³ã 㯠onSubmit ããããã£ïŒç»é¢ã§ submit ãã¿ã³ãæŒãããéã«èµ°ãåŠçïŒã®æå®ãå¿
é ã§ãããäžæŠã¯ãç»é¢ãéããåŠçãå
¥ããŠãããŸãã ããã§ã¯ãç»é¢ã§ç¢ºèªããŠã¿ãŸãããããã ã®åã«ãã¢ããªã®ã¹ã³ãŒãã倿Žããã®ã§ãã¢ããã°ã¬ãŒããå¿
èŠã§ãã以äžã®ã³ãã³ãã§ã¢ããã°ã¬ãŒããå®è¡ããŸãã forge install --upgrade ããã§ã¢ããªãå©çšå¯èœã«ãªããŸããã 確èªããŠã¿ããšã以äžã®ããã«ãèŠçŽæ¬ã®æ
å ±ãååŸã§ããããã«ãªã£ãŠãããšæããŸãã ã³ãŒãå
šæãèŠã // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; if (!isOpen) { return null; } return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={() => setOpen(false)}>{makeTextField(responseJson)}</Form> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); â§ ç·šéå
容ãæåºã§ããããã«ãã ããã§ã¯æåŸã«ãç·šéããå
容ãæåºãã倿Žãåæ ã§ããããã«ããŸãã ç·šéå
容ã®ç¢ºå®ã«ã¯ãEdit issueããšãã API ã䜿çšããŸãããã® API ãåŒã³ã ã颿°ãäœæããŸãããã®éããªã¯ ãšã¹ ãããã£ã«ã¯ãsubmit ãã¿ã³ãæŒãããéã«éãããŠããããŒã¿ãããšã«äœæãã JSON ãæå®ããŸãã // src/index.tsx const execEdit = async (submitted: {summary: string}) => { await api.asApp().requestJira(route`/rest/api/3/issue/${requestId}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: `{"fields":{"summary":"${submitted["summary"]}"}}`, }); setOpen(false); }; ãããŠããã®é¢æ°ã Form ã³ã³ããŒãã³ã ã® onSubmit ããããã£ã§æå®ããŸãã // src/index.tsx return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={execEdit}>{makeTextField(responseJson)}</Form> </ModalDialog> ); ããã§OKã§ãã ç»é¢ã§ç¢ºèªããåã«ãæ°ãã« API ã远å ããã®ã§ãæžãèŸŒã¿æš©éãã¢ããªã«äžããŠã¢ããã°ã¬ãŒãããŸãããã // manifest.yml permissions: scopes: - "read:servicedesk-request" - "read:jira-work" - "write:jira-work" 以äžã§å
šãŠã®å·¥çšãå®äºã§ããå®éã«äœ¿ã£ãŠã¿ãŸãã èŠçŽæ¬ãç·šéããsubmit ãæŒããŠâŠãªããŒããããšâŠ åæ ãããŠããŸãïŒ ã³ãŒãå
šæãèŠã // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; const execEdit = async (submitted: { summary: string }) => { await api.asApp().requestJira(route`/rest/api/3/issue/${requestId}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: `{"fields":{"summary":"${submitted["summary"]}"}}`, }); setOpen(false); }; if (!isOpen) { return null; } return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={execEdit}>{makeTextField(responseJson)}</Form> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); éçºã䟿å©ã«ãã Tips éçºæé ã®ç޹ä»ã¯ä»¥äžã§ãããéçºã䟿å©ã«ãã Tips ã玹ä»ããŠãããŸãã â ãã°ã®åºãæ¹ ãã°ãåºãããç®æã« console.log("åºãããå
容") ãå
¥ãã forge logs ãå®è¡ããããšã§ããã°ã確èªã§ããŸãã ãã¡ãã¡äžèšã³ãã³ããæã€ã®ã¯é¢åã§ãããæ¬¡ã«ç޹ä»ãã Tunnel ã¢ãŒããçµã¿åãããããšã§è§£æ¶ãããŸãã â¡ Tunnel ã¢ãŒã ãã¡ã€ã«ä¿åæã«èªåçã«åãã«ããå®è¡ããŠãããæ©èœã§ãããã ããdevelopment ç°å¢ãžã¢ããªããããã€ããŠããå Žåã«éå®ãããŸãããã¡ãã¡ forge deploy ã³ãã³ããæã€æéãçããã»ããåè¿°ãããã°ããªã¢ã«ã¿ã€ã ã§åºããŠããããããéåžžã«äŸ¿å©ã§ãã äœ¿ãæ¹ã¯ä»¥äžã確èªããŠãã ããã https://developer.atlassian.com/platform/forge/tunneling/ 泚æç¹ãšããŠãããŸã«ãäœã®ãšã©ãŒãåºãŠããªãã«ãããããã倿Žãåæ ãããªãå ŽåããããŸãããææ¡ããŠãããªããšæ°žé ã«æéãæº¶ããããšã«ãªãã®ã§æ°ãã€ããŠãã ããã ⢠ã¢ãžã¥ãŒã«ã®è¿œå æ¹æ³ forge create ã³ãã³ãã§ãããžã§ã¯ããäœæããçŽåŸã¯ã¢ãžã¥ãŒã«ã¯1ã€ã ãã§ãããåœç¶è€æ°ã¢ãžã¥ãŒã«ãæ±ãããšãã§ããŸãã ãããã§ã¹ã ãã¡ã€ã« (manifest.yml) ã«è¿œå ããã°OKã§ãã以äžã®ãããªã€ã¡ãŒãžã // manifest.yml modules: jiraServiceManagement:portalRequestViewAction: - key: module1 function: func1 title: ã¢ãžã¥ãŒã«1 jiraServiceManagement:queuePage: - key: module2 function: func2 title: ã¢ãžã¥ãŒã«2 function: - key: func1 handler: index.run1 - key: func2 handler: index.run2 衚瀺ãããã¿ã³ã®è¡šèšãã¢ã€ã³ã³ããã¿ã³ãæŒããéã«æåã«åŒã³åºããã颿°ãªã©ã倿Žãããå Žåã ãããã§ã¹ã ãã¡ã€ã«ãããããŸãã UI kit ã䜿ã£ãŠã¿ãææ³ æ¬èšäºã§ã¯ UI kit ã䜿ã£ãŠéçºãé²ããŠããŸããããã¡ãªããã»ãã¡ãªãããšãã«åŒ·ãæãããããææ³ãèšããŠãããŸãïŒããããã³ã³ã»ããéãã®ææ³ã§ããïŒã ã¡ãªãã ãšã«ããéçºãéããŠç°¡åã§ãããã¶ã€ã³ã®ããšã¯äœãèããªããŠè¯ãã§ãïŒèããäœå°ããªããšãèšãïŒã ã³ã³ããŒãã³ã ã®çš®é¡ãå²ãšè±å¯ã§ãã·ã³ãã«ãªæ©èœã远å ããã ãã§ããã°ããŸãå°ããªããšæããŸãã ãã¡ãªãã æ³å以äžã«èªç±ããããŸããã§ãããçŸç¶ã ãšãæåã®è²ããµã€ãºã ã³ã³ããŒãã³ã éã®ééãªã©ã®ã¡ãã£ãšãããšãããå€ããããŸããããŸããä»å玹ä»ããç·šéæ©èœã«é¢ããŠèšããšããã£ãŒã«ãã«å¯Ÿå¿ãã ã³ã³ããŒãã³ã ãæäŸãããŠãããã工倫ããŠãã©ããããããªãå ŽåããããŸããïŒãªããããã¹ãã§å
¥åããæ¬ãªã©ïŒã ã¬ã³ããªã³ã° é床ãé
ãã§ããæ¯åããã¯ãšã³ããçµç±ããŠããã®ã§ä»æ¹ãªãã®ã§ãããå
¥åå€ã®ããªããŒã·ã§ã³ãªã©ã§ã¯ç¹ã«æ°ã«ãªããŸãã æ©èœãè€éåããŠãããšã©ãããŠã UI kit ã§ã¯ç©è¶³ããªãå Žé¢ããããããä»åŸã¯ Custom UI ã䜿çšããéçºã«ãåãçµãã§ãããããšæããŸãã ãããã« ä»åã¯ãAtlassian Forge ã®æŠèŠãšå®éã®éçºæé ã説æããŸãããããŒã¿ã®ååŸã»ç»é¢è¡šç€ºã»ç»é²ãšãåºæ¬çãªåäœãã«ããŒããã€ããã§ããããããªããŒã«ã§ã¯ãããŸãããæ¬èšäºã誰ãã®ã圹ã«ç«ãŠã°å¹žãã§ãã ç§ãã¡ã¯åãããŒã ã§åããŠããã仲éãæ¢ããŠããŸããä»åã®ãšã³ããªã§ç޹ä»ãããããªä»äºã«èå³ã®ããæ¹ããå¿åãåŸ
ã¡ããŠããŸãã ãœãªã¥ãŒã·ã§ã³ã¢ãŒããã¯ã å·çïŒ @matsu ïŒ Shodo ã§å·çãããŸãã ïŒ