@valiantys/atlassian-app-frontend 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +726 -0
  2. package/atlassian-app-forge-CTaVjJLt.js +1 -0
  3. package/atlassian-app-forge-DdtDadi2.mjs +117 -0
  4. package/atlassian-app-frontend.api.json +8829 -0
  5. package/atlassian-app-frontend.api.md +740 -0
  6. package/atlassian-app-standalone-DJOVbfp6.js +1 -0
  7. package/atlassian-app-standalone-DyyH6WPO.mjs +111 -0
  8. package/examples/backend/index.ts +44 -0
  9. package/examples/backend/lib/forge-functions.d.ts +3 -0
  10. package/examples/backend/lib/forge-functions.ts +23 -0
  11. package/examples/backend/lib/handler-functions.d.ts +58 -0
  12. package/examples/backend/lib/handler-functions.ts +72 -0
  13. package/examples/backend/lib/standalone-functions.d.ts +3 -0
  14. package/examples/backend/lib/standalone-functions.ts +17 -0
  15. package/examples/client-sample-.env +5 -0
  16. package/examples/hello-world/app.d.ts +2 -0
  17. package/examples/hello-world/app.tsx +77 -0
  18. package/examples/hello-world/example-box.d.ts +8 -0
  19. package/examples/hello-world/example-box.tsx +22 -0
  20. package/examples/hello-world/example-components/forge-storage-example.d.ts +1 -0
  21. package/examples/hello-world/example-components/forge-storage-example.tsx +66 -0
  22. package/examples/hello-world/example-components/hello-with-loading-spinner.d.ts +1 -0
  23. package/examples/hello-world/example-components/hello-with-loading-spinner.tsx +24 -0
  24. package/examples/hello-world/example-components/hello.d.ts +1 -0
  25. package/examples/hello-world/example-components/hello.spec.tsx +39 -0
  26. package/examples/hello-world/example-components/hello.tsx +13 -0
  27. package/examples/hello-world/example-components/host-router-example.d.ts +1 -0
  28. package/examples/hello-world/example-components/host-router-example.tsx +13 -0
  29. package/examples/hello-world/example-components/issue-types-example-backend.d.ts +4 -0
  30. package/examples/hello-world/example-components/issue-types-example-backend.tsx +37 -0
  31. package/examples/hello-world/example-components/issue-types-example.d.ts +4 -0
  32. package/examples/hello-world/example-components/issue-types-example.tsx +40 -0
  33. package/examples/hello-world/example-components/list-assets.d.ts +1 -0
  34. package/examples/hello-world/example-components/list-assets.tsx +38 -0
  35. package/examples/hello-world/example-components/open-modal-example.d.ts +1 -0
  36. package/examples/hello-world/example-components/open-modal-example.tsx +47 -0
  37. package/examples/hello-world/example-components/view-context-example.d.ts +1 -0
  38. package/examples/hello-world/example-components/view-context-example.tsx +17 -0
  39. package/examples/hello-world/example-components/who-am-i.d.ts +1 -0
  40. package/examples/hello-world/example-components/who-am-i.tsx +13 -0
  41. package/examples/hello-world/main.d.ts +0 -0
  42. package/examples/hello-world/main.tsx +11 -0
  43. package/examples/hello-world/styles.css +1 -0
  44. package/examples/hello-world-modal/app.d.ts +2 -0
  45. package/examples/hello-world-modal/app.tsx +30 -0
  46. package/examples/hello-world-modal/hello.d.ts +1 -0
  47. package/examples/hello-world-modal/hello.tsx +15 -0
  48. package/examples/hello-world-modal/main.d.ts +0 -0
  49. package/examples/hello-world-modal/main.tsx +11 -0
  50. package/examples/hello-world-modal/styles.css +1 -0
  51. package/examples/hello-world-remote/app.d.ts +2 -0
  52. package/examples/hello-world-remote/app.tsx +23 -0
  53. package/examples/hello-world-remote/invoke-remote-example.d.ts +13 -0
  54. package/examples/hello-world-remote/invoke-remote-example.tsx +40 -0
  55. package/examples/hello-world-remote/main.d.ts +0 -0
  56. package/examples/hello-world-remote/main.tsx +11 -0
  57. package/examples/hello-world-remote/styles.css +1 -0
  58. package/examples/manifest.yml.example +49 -0
  59. package/index-CBKhl1FP.mjs +22 -0
  60. package/index-CP8emE0q.js +1 -0
  61. package/index.d.ts +654 -0
  62. package/index.js +2 -0
  63. package/index.mjs +1145 -0
  64. package/package.json +54 -0
  65. package/style.css +1 -0
  66. package/tsdoc-metadata.json +11 -0
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),g=require("@atlaskit/app-provider"),o=require("react"),e=require("./index.js");require("@atlaskit/heading");require("@atlaskit/primitives");require("@atlaskit/button");require("@atlaskit/button/new");require("@atlaskit/form");require("@atlaskit/section-message");require("@atlaskit/select");require("@atlaskit/spinner");require("react-router-dom");require("@atlaskit/flag");require("@atlaskit/icon/glyph/check-circle");require("@atlaskit/icon/glyph/error");require("@atlaskit/icon/glyph/info");require("@atlaskit/icon/glyph/warning");require("@atlaskit/tokens");const w=o.lazy(()=>Promise.resolve().then(()=>require("./index-CP8emE0q.js")));function k({initialMockViewContext:c,backendUrl:i,remoteUrl:p,doCheckWorkspace:m,appName:a,doCheckResource:h,modalOpenerConfig:v,modalContextConfig:I,children:A}){const[u,S]=o.useState(),[n,j]=o.useState(),{getAuthToken:s}=e.useOAuth(),x=o.useCallback(()=>e.atlassianOAuthJiraFetch(s(),u??""),[s,u]),R=o.useCallback(()=>e.atlassianOAuthConfluenceFetch(s(),u??""),[s,u]),C=o.useCallback(()=>e.atlassianOAuthBitbucketFetch(s()),[s]),P=o.useCallback(async l=>{if(i&&h){const q=l??localStorage.getItem(`${a}-user-resource-id`)??"",d=await new e.AtlassianUserResourceService().checkUserChosenResource(s(),q);return d.hasChosen&&(localStorage.setItem(`${a}-user-resource-id`,d.resource.id),S(d.resource.id)),d}else return{hasChosen:!0}},[i,s,a,h]);o.useEffect(()=>{const l=u??(c==null?void 0:c.cloudId)??"";if(!n||n.cloudId!==l){const q={...n||c,cloudId:l};j(q)}},[c,u,n]);const F=o.useCallback(()=>i?e.getOauthFetchInvokeImpl(i,s(),u??""):void 0,[u,s,i]);let r=A;m&&(r=t.jsx(e.FeatureChosenWorkspaceCheck,{appName:a,children:r})),v&&(r=t.jsx(e.ModalServiceProvider,{modalService:new e.WindowModalService(v.defaultUrl,v.resourceMap),children:r})),n&&(r=t.jsx(e.ViewContextProvider,{viewContext:I?new e.ModalViewContext(n,I.openerOrigin):new e.MockViewContext(n),children:r})),r=t.jsx(e.HostRouterProvider,{hostRouter:new e.WindowHostRouter,children:t.jsx(e.RequestBitbucketProvider,{requestBitbucketImplementation:C(),children:t.jsx(e.RequestConfluenceProvider,{requestConfluenceImplementation:R(),children:t.jsx(e.RequestJiraProvider,{requestJiraImplementation:x(),children:r})})})});const f=F();return f&&(r=t.jsx(e.BackendAdapterProvider,{invokeImplementation:f,children:r})),p&&(r=t.jsx(e.RemoteAdapterProvider,{invokeRemoteImplementation:e.oauthFetchInvokeRemote(p),children:r})),h&&(r=t.jsx(w,{checkResource:P,children:r})),t.jsx(g,{children:t.jsx(e.AppFlagsProviderAtlasKit,{children:r})})}exports.AtlassianAppStandalone=k;exports.default=k;
@@ -0,0 +1,111 @@
1
+ import { jsx as t } from "react/jsx-runtime";
2
+ import q from "@atlaskit/app-provider";
3
+ import { lazy as B, useState as I, useCallback as i, useEffect as W } from "react";
4
+ import { useOAuth as J, atlassianOAuthJiraFetch as O, atlassianOAuthConfluenceFetch as x, atlassianOAuthBitbucketFetch as y, AtlassianUserResourceService as E, getOauthFetchInvokeImpl as H, ModalServiceProvider as T, WindowModalService as $, ViewContextProvider as b, ModalViewContext as j, MockViewContext as z, HostRouterProvider as K, WindowHostRouter as M, RequestBitbucketProvider as D, RequestConfluenceProvider as G, RequestJiraProvider as L, BackendAdapterProvider as Q, RemoteAdapterProvider as X, oauthFetchInvokeRemote as Y, AppFlagsProviderAtlasKit as Z, FeatureChosenWorkspaceCheck as _ } from "./index.mjs";
5
+ import "@atlaskit/heading";
6
+ import "@atlaskit/primitives";
7
+ import "@atlaskit/button";
8
+ import "@atlaskit/button/new";
9
+ import "@atlaskit/form";
10
+ import "@atlaskit/section-message";
11
+ import "@atlaskit/select";
12
+ import "@atlaskit/spinner";
13
+ import "react-router-dom";
14
+ import "@atlaskit/flag";
15
+ import "@atlaskit/icon/glyph/check-circle";
16
+ import "@atlaskit/icon/glyph/error";
17
+ import "@atlaskit/icon/glyph/info";
18
+ import "@atlaskit/icon/glyph/warning";
19
+ import "@atlaskit/tokens";
20
+ const U = B(
21
+ () => import("./index-CBKhl1FP.mjs")
22
+ );
23
+ function Ie({
24
+ initialMockViewContext: s,
25
+ backendUrl: c,
26
+ remoteUrl: p,
27
+ doCheckWorkspace: A,
28
+ appName: u,
29
+ doCheckResource: h,
30
+ modalOpenerConfig: l,
31
+ modalContextConfig: v,
32
+ children: R
33
+ }) {
34
+ const [o, k] = I(), [n, F] = I(), { getAuthToken: r } = J(), S = i(() => O(r(), o ?? ""), [r, o]), P = i(() => x(r(), o ?? ""), [r, o]), g = i(() => y(r()), [r]), w = i(
35
+ async (d) => {
36
+ if (c && h) {
37
+ const m = d ?? localStorage.getItem(`${u}-user-resource-id`) ?? "", a = await new E().checkUserChosenResource(
38
+ r(),
39
+ m
40
+ );
41
+ return a.hasChosen && (localStorage.setItem(
42
+ `${u}-user-resource-id`,
43
+ a.resource.id
44
+ ), k(a.resource.id)), a;
45
+ } else
46
+ return { hasChosen: !0 };
47
+ },
48
+ [c, r, u, h]
49
+ );
50
+ W(() => {
51
+ const d = o ?? (s == null ? void 0 : s.cloudId) ?? "";
52
+ if (!n || n.cloudId !== d) {
53
+ const m = {
54
+ ...n || s,
55
+ cloudId: d
56
+ };
57
+ F(m);
58
+ }
59
+ }, [s, o, n]);
60
+ const C = i(
61
+ () => c ? H(c, r(), o ?? "") : void 0,
62
+ [o, r, c]
63
+ );
64
+ let e = R;
65
+ A && (e = /* @__PURE__ */ t(_, { appName: u, children: e })), l && (e = /* @__PURE__ */ t(
66
+ T,
67
+ {
68
+ modalService: new $(
69
+ l.defaultUrl,
70
+ l.resourceMap
71
+ ),
72
+ children: e
73
+ }
74
+ )), n && (e = /* @__PURE__ */ t(
75
+ b,
76
+ {
77
+ viewContext: v ? new j(n, v.openerOrigin) : new z(n),
78
+ children: e
79
+ }
80
+ )), e = /* @__PURE__ */ t(K, { hostRouter: new M(), children: /* @__PURE__ */ t(
81
+ D,
82
+ {
83
+ requestBitbucketImplementation: g(),
84
+ children: /* @__PURE__ */ t(
85
+ G,
86
+ {
87
+ requestConfluenceImplementation: P(),
88
+ children: /* @__PURE__ */ t(
89
+ L,
90
+ {
91
+ requestJiraImplementation: S(),
92
+ children: e
93
+ }
94
+ )
95
+ }
96
+ )
97
+ }
98
+ ) });
99
+ const f = C();
100
+ return f && (e = /* @__PURE__ */ t(Q, { invokeImplementation: f, children: e })), p && (e = /* @__PURE__ */ t(
101
+ X,
102
+ {
103
+ invokeRemoteImplementation: Y(p),
104
+ children: e
105
+ }
106
+ )), h && (e = /* @__PURE__ */ t(U, { checkResource: w, children: e })), /* @__PURE__ */ t(q, { children: /* @__PURE__ */ t(Z, { children: e }) });
107
+ }
108
+ export {
109
+ Ie as AtlassianAppStandalone,
110
+ Ie as default
111
+ };
@@ -0,0 +1,44 @@
1
+ import {
2
+ bootstrapExpress,
3
+ expressDefaults,
4
+ registerFunctions,
5
+ } from '@valiantys/atlassian-app-backend';
6
+
7
+ import Resolver from '@forge/resolver';
8
+ import * as dotenv from 'dotenv';
9
+
10
+ import { getForgeFunctions } from './lib/forge-functions';
11
+ import { getStandaloneFunctions } from './lib/standalone-functions';
12
+
13
+ dotenv.config();
14
+
15
+ const IS_STANDALONE = process.env.IS_STANDALONE === 'true';
16
+
17
+ if (IS_STANDALONE) {
18
+ const port = parseInt(process.env.PORT || '3000');
19
+
20
+ // OAuth settings
21
+ const clientId = process.env.ATLASSIAN_OAUTH_CLIENT_ID || '';
22
+ const clientSecret = process.env.ATLASSIAN_OAUTH_CLIENT_SECRET || '';
23
+
24
+ // Mock Forge storage settings
25
+ const filePath = process.env.FORGE_STORAGE_FILE_PATH || '';
26
+ const indexFieldMapping = JSON.parse(
27
+ process.env.FORGE_STORAGE_INDEX_FIELD_MAPPING || '{}'
28
+ );
29
+
30
+ bootstrapExpress(
31
+ getStandaloneFunctions(),
32
+ 'jira',
33
+ { clientId, clientSecret },
34
+ { ...expressDefaults, port },
35
+ { indexFieldMapping, filePath }
36
+ );
37
+ }
38
+
39
+ let resolver: Resolver | null = null;
40
+ if (!IS_STANDALONE) {
41
+ resolver = new Resolver();
42
+ registerFunctions(resolver, getForgeFunctions());
43
+ }
44
+ export const handler = resolver?.getDefinitions();
@@ -0,0 +1,3 @@
1
+ import { ResolverFunctionMap } from '../../../../../atlassian-app-backend/src/index.ts';
2
+
3
+ export declare function getForgeFunctions(): ResolverFunctionMap;
@@ -0,0 +1,23 @@
1
+ import { storage, startsWith, WhereConditions } from '@forge/api';
2
+ import {
3
+ createServerSideForgeProductFetchService,
4
+ ResolverFunctionMap,
5
+ } from '@valiantys/atlassian-app-backend';
6
+
7
+ import { handlerFunctions } from './handler-functions';
8
+
9
+ export function getForgeFunctions(): ResolverFunctionMap {
10
+ const productFetchService = createServerSideForgeProductFetchService(
11
+ 'user',
12
+ 'jira'
13
+ );
14
+ const forgeStorageService = {
15
+ storage,
16
+ WhereConditions,
17
+ startsWith,
18
+ };
19
+
20
+ // Here you could initialize any other deployed-specific services or config
21
+ // to pass to handlerFunctions as needed.
22
+ return handlerFunctions(productFetchService, forgeStorageService);
23
+ }
@@ -0,0 +1,58 @@
1
+ import {
2
+ AtlassianProductFetchService,
3
+ ForgeRequest,
4
+ ForgeStorageService,
5
+ } from '../../../../../atlassian-app-backend/src/index.ts';
6
+
7
+ export declare function handlerFunctions(
8
+ productFetchService: AtlassianProductFetchService<'jira'>,
9
+ forgeStorageService: ForgeStorageService
10
+ ): {
11
+ getText: {
12
+ function: () => {
13
+ text: string;
14
+ };
15
+ };
16
+ getIssueTypes: {
17
+ function: () => Promise<IssueTypeDetails[]>;
18
+ };
19
+ /**
20
+ * Forge storage examples
21
+ */
22
+ getMyName: {
23
+ function: () => Promise<{
24
+ name: any;
25
+ }>;
26
+ };
27
+ setMyName: {
28
+ function: ({
29
+ payload,
30
+ }: ForgeRequest<{
31
+ name: string;
32
+ }>) => Promise<{
33
+ name: any;
34
+ }>;
35
+ updatesForgeStorage: boolean;
36
+ };
37
+ /**
38
+ * This example demonstrates how one can access the user's account ID
39
+ * in either deployed or standalone mode.
40
+ * This context is made available on every backend request.
41
+ */
42
+ whoAmI: {
43
+ function: ({ context }: ForgeRequest<void>) => Promise<{
44
+ myId: string;
45
+ }>;
46
+ };
47
+ };
48
+ interface IssueTypeDetails {
49
+ avatarId: number;
50
+ description: string;
51
+ hierarchyLevel: number;
52
+ iconUrl: string;
53
+ id: string;
54
+ name: string;
55
+ self: string;
56
+ subtask: boolean;
57
+ }
58
+ export {};
@@ -0,0 +1,72 @@
1
+ import {
2
+ AtlassianProductFetchService,
3
+ ForgeRequest,
4
+ ForgeStorageService,
5
+ } from '@valiantys/atlassian-app-backend';
6
+
7
+ // Add any additional environment specific config/services as parameters
8
+ // to handlerFunctions()
9
+ export function handlerFunctions(
10
+ productFetchService: AtlassianProductFetchService<'jira'>,
11
+ forgeStorageService: ForgeStorageService
12
+ ) {
13
+ return {
14
+ getText: {
15
+ function: () => {
16
+ return { text: 'Hello world!' };
17
+ },
18
+ },
19
+
20
+ /*
21
+ Making an Atlassian product API request (requestJira)
22
+ */
23
+ getIssueTypes: {
24
+ function: () =>
25
+ productFetchService.fetch<IssueTypeDetails[]>({
26
+ url: productFetchService.route`/rest/api/2/issuetype`,
27
+ method: 'GET',
28
+ }),
29
+ },
30
+
31
+ /**
32
+ * Forge storage examples
33
+ */
34
+ getMyName: {
35
+ function: async () => {
36
+ const name = await forgeStorageService.storage.get('myName');
37
+ return { name };
38
+ },
39
+ },
40
+ setMyName: {
41
+ function: async ({ payload }: ForgeRequest<{ name: string }>) => {
42
+ await forgeStorageService.storage.set('myName', payload.name);
43
+ const name = await forgeStorageService.storage.get('myName');
44
+ return { name };
45
+ },
46
+ updatesForgeStorage: true, // triggers save of local file when in standalone mode
47
+ },
48
+
49
+ /**
50
+ * This example demonstrates how one can access the user's account ID
51
+ * in either deployed or standalone mode.
52
+ * This context is made available on every backend request.
53
+ */
54
+ whoAmI: {
55
+ function: async ({ context }: ForgeRequest<void>) => {
56
+ console.log(context.accountId);
57
+ return { myId: context.accountId };
58
+ },
59
+ },
60
+ };
61
+ }
62
+
63
+ interface IssueTypeDetails {
64
+ avatarId: number;
65
+ description: string;
66
+ hierarchyLevel: number;
67
+ iconUrl: string;
68
+ id: string;
69
+ name: string;
70
+ self: string;
71
+ subtask: boolean;
72
+ }
@@ -0,0 +1,3 @@
1
+ import { ResolverFunctionFactory } from '../../../../../atlassian-app-backend/src/index.ts';
2
+
3
+ export declare function getStandaloneFunctions(): ResolverFunctionFactory<'jira'>;
@@ -0,0 +1,17 @@
1
+ import {
2
+ AtlassianProductFetchService,
3
+ ForgeStorageServiceStandalone,
4
+ ResolverFunctionFactory,
5
+ } from '@valiantys/atlassian-app-backend';
6
+ import { handlerFunctions } from './handler-functions';
7
+
8
+ export function getStandaloneFunctions(): ResolverFunctionFactory<'jira'> {
9
+ return (
10
+ productFetchService: AtlassianProductFetchService<'jira'>,
11
+ forgeStorageService: ForgeStorageServiceStandalone
12
+ ) => {
13
+ // Here you could initialize any other standalone-specific services or config
14
+ // to pass to handlerFunctions as needed.
15
+ return handlerFunctions(productFetchService, forgeStorageService);
16
+ };
17
+ }
@@ -0,0 +1,5 @@
1
+ VITE_ATLASSIAN_OAUTH_CLIENT_ID=<ATLASSIAN_OAUTH_CLIENT_ID>
2
+
3
+ # Set to deployed url/port in non local environment
4
+ VITE_STANDALONE_BACKEND_URL='http://localhost'
5
+ VITE_STANDALONE_BACKEND_PORT=3000
@@ -0,0 +1,2 @@
1
+ declare function App(): import('react/jsx-runtime').JSX.Element;
2
+ export default App;
@@ -0,0 +1,77 @@
1
+ import {
2
+ AtlassianApp,
3
+ CommonResolverPaths,
4
+ } from '@valiantys/atlassian-app-frontend';
5
+
6
+ import { Hello } from './example-components/hello';
7
+ import { HelloWithLoadingSpinner } from './example-components/hello-with-loading-spinner';
8
+ import { HostRouterExample } from './example-components/host-router-example';
9
+ import { ListAssets } from './example-components/list-assets';
10
+ import { ViewContextExample } from './example-components/view-context-example';
11
+ import { OpenModalExample } from './example-components/open-modal-example';
12
+ import { IssueTypesExample } from './example-components/issue-types-example';
13
+ import { ExampleBox } from './example-box';
14
+ import { ForgeStorageExample } from './example-components/forge-storage-example';
15
+ import { IssueTypesExampleBackend } from './example-components/issue-types-example-backend';
16
+ import { WhoAmI } from './example-components/who-am-i';
17
+
18
+ const backendUrl = `${import.meta.env.VITE_STANDALONE_BACKEND_URL}:${import.meta.env.VITE_STANDALONE_BACKEND_PORT}/api/`;
19
+
20
+ function App() {
21
+ return (
22
+ <AtlassianApp
23
+ appName="hello"
24
+ doCheckWorkspace={true} // Needed for querying JSM Assets
25
+ standaloneConfig={{
26
+ backendUrl,
27
+ oauthConfig: {
28
+ appName: 'hello',
29
+ codeTokenExchangeUrl: `${backendUrl}${CommonResolverPaths.getOauthToken}`,
30
+ clientId: import.meta.env.VITE_ATLASSIAN_OAUTH_CLIENT_ID,
31
+ oAuthScopes: [
32
+ 'read:jira-user',
33
+ 'read:me',
34
+ 'read:jira-work', // needed to query for Jira issues
35
+ 'read:servicedesk-request', // needed to query for assets workspace ID
36
+ 'read:cmdb-schema:jira', // needed to query for list of assets schemas
37
+ ],
38
+ },
39
+ modalOpenerConfig: { defaultUrl: 'http://localhost:4201/' },
40
+ }}
41
+ embeddedConfig={{}}
42
+ >
43
+ <ExampleBox title="Simple Invoke Example">
44
+ <Hello></Hello>
45
+ </ExampleBox>
46
+ <ExampleBox title="useLoadDataEffect Example">
47
+ <HelloWithLoadingSpinner></HelloWithLoadingSpinner>
48
+ </ExampleBox>
49
+ <ExampleBox title="useHostRouter Example">
50
+ <HostRouterExample></HostRouterExample>
51
+ </ExampleBox>
52
+ <ExampleBox title="useWorkspaceId and useRequestJira Example">
53
+ Asset Schemas: <ListAssets></ListAssets>
54
+ </ExampleBox>
55
+ <ExampleBox title="useViewContext Example">
56
+ <ViewContextExample></ViewContextExample>
57
+ </ExampleBox>
58
+ <ExampleBox title="Open Modal Example">
59
+ <OpenModalExample></OpenModalExample>
60
+ </ExampleBox>
61
+ <ExampleBox title="Calling Atlassian Product APIs from the frontend">
62
+ <IssueTypesExample></IssueTypesExample>
63
+ </ExampleBox>
64
+ <ExampleBox title="Calling Atlassian Product APIs from the backend">
65
+ <IssueTypesExampleBackend></IssueTypesExampleBackend>
66
+ </ExampleBox>
67
+ <ExampleBox title="Forge Storage Example">
68
+ <ForgeStorageExample></ForgeStorageExample>
69
+ </ExampleBox>
70
+ <ExampleBox title="Who Am I Example">
71
+ <WhoAmI></WhoAmI>
72
+ </ExampleBox>
73
+ </AtlassianApp>
74
+ );
75
+ }
76
+
77
+ export default App;
@@ -0,0 +1,8 @@
1
+ import { PropsWithChildren } from 'react';
2
+
3
+ export declare function ExampleBox({
4
+ title,
5
+ children,
6
+ }: PropsWithChildren<{
7
+ title: string;
8
+ }>): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,22 @@
1
+ import Heading from '@atlaskit/heading';
2
+ import { Box, xcss } from '@atlaskit/primitives';
3
+ import { PropsWithChildren } from 'react';
4
+
5
+ export function ExampleBox({
6
+ title,
7
+ children,
8
+ }: PropsWithChildren<{ title: string }>) {
9
+ return (
10
+ <Box
11
+ padding="space.200"
12
+ xcss={xcss({
13
+ borderColor: 'color.border',
14
+ borderWidth: '1px',
15
+ borderStyle: 'solid',
16
+ })}
17
+ >
18
+ <Heading size="small">{title}</Heading>
19
+ {children}
20
+ </Box>
21
+ );
22
+ }
@@ -0,0 +1 @@
1
+ export declare function ForgeStorageExample(): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,66 @@
1
+ import { ButtonGroup } from '@atlaskit/button';
2
+ import Button from '@atlaskit/button/new';
3
+ import Form, { Field } from '@atlaskit/form';
4
+ import { Box, Flex } from '@atlaskit/primitives';
5
+ import Textfield from '@atlaskit/textfield';
6
+ import { useBackendAdapter } from '@valiantys/atlassian-app-frontend';
7
+ import { useCallback, useEffect, useState } from 'react';
8
+
9
+ interface FormValue {
10
+ name?: string;
11
+ }
12
+
13
+ export function ForgeStorageExample() {
14
+ const [myName, setMyName] = useState<string | undefined>();
15
+ const { invoke } = useBackendAdapter();
16
+
17
+ const saveForm = useCallback(
18
+ async (formVal: FormValue) => {
19
+ try {
20
+ const updated = await invoke<FormValue, FormValue>(
21
+ 'setMyName',
22
+ formVal
23
+ );
24
+ console.log({ updated });
25
+ setMyName(updated.name || 'World');
26
+ } catch (error) {
27
+ console.error(error);
28
+ }
29
+ },
30
+ [invoke]
31
+ );
32
+
33
+ useEffect(() => {
34
+ invoke<{ name?: string }>('getMyName').then((r) =>
35
+ setMyName(r.name || 'World')
36
+ );
37
+ }, [invoke]);
38
+
39
+ return myName ? (
40
+ <Flex>
41
+ <Form<FormValue> onSubmit={saveForm}>
42
+ {({ formProps, submitting }) => (
43
+ <form {...formProps}>
44
+ <Field
45
+ aria-required={true}
46
+ name="name"
47
+ label="My name"
48
+ isRequired
49
+ defaultValue={myName}
50
+ >
51
+ {({ fieldProps }) => <Textfield {...fieldProps} />}
52
+ </Field>
53
+ <ButtonGroup>
54
+ <Button type="submit" appearance="primary" isLoading={submitting}>
55
+ Update
56
+ </Button>
57
+ </ButtonGroup>
58
+ </form>
59
+ )}
60
+ </Form>
61
+ <Box padding="space.500">Hello {myName}!</Box>
62
+ </Flex>
63
+ ) : (
64
+ <div>Hello!</div>
65
+ );
66
+ }
@@ -0,0 +1 @@
1
+ export declare function HelloWithLoadingSpinner(): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import {
4
+ PageLoadingView,
5
+ useBackendAdapter,
6
+ useLoadDataEffect,
7
+ } from '@valiantys/atlassian-app-frontend';
8
+
9
+ export function HelloWithLoadingSpinner() {
10
+ const { invoke } = useBackendAdapter();
11
+
12
+ const requestFunction = useCallback(() => {
13
+ return invoke<{ text: string }>('getText');
14
+ }, [invoke]);
15
+
16
+ const { data, loading, error } = useLoadDataEffect<{ text: string }>(
17
+ requestFunction
18
+ );
19
+ return loading || error ? (
20
+ <PageLoadingView label="" loadingError={error} />
21
+ ) : (
22
+ <div>{data?.text}</div>
23
+ );
24
+ }
@@ -0,0 +1 @@
1
+ export declare function Hello(): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,39 @@
1
+ /***
2
+ Testing references:
3
+ https://testing-library.com/docs/react-testing-library/example-intro
4
+ https://testing-library.com/docs/queries/about#:~:text=queryOptions%2C%20waitForOptions))-,Summary,-Table
5
+ ***/
6
+
7
+ import { act, render, screen } from '@testing-library/react';
8
+
9
+ import {
10
+ AtlassianAppTest,
11
+ AtlassianAppTestProps,
12
+ defaultProps,
13
+ } from '@valiantys/atlassian-app-frontend';
14
+
15
+ import { Hello } from './hello';
16
+
17
+ describe('HelloWorld', () => {
18
+ it('should render successfully', async () => {
19
+ await act(async () => {
20
+ // Setup mocks
21
+ const mockInvoke = jest.fn();
22
+ mockInvoke.mockReturnValue(Promise.resolve({ text: 'Hello World' }));
23
+ const appProps: AtlassianAppTestProps = {
24
+ ...defaultProps(jest.fn),
25
+ invoke: mockInvoke,
26
+ };
27
+
28
+ // Render the component (which calls invoke on first render)
29
+ render(
30
+ <AtlassianAppTest {...appProps}>
31
+ <Hello></Hello>
32
+ </AtlassianAppTest>
33
+ );
34
+ });
35
+
36
+ // Throws error if text is not found within timeout period
37
+ await screen.findByText('Hello World');
38
+ });
39
+ });
@@ -0,0 +1,13 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useBackendAdapter } from '@valiantys/atlassian-app-frontend';
3
+
4
+ export function Hello() {
5
+ const [text, setText] = useState<string>('');
6
+ const { invoke } = useBackendAdapter();
7
+
8
+ useEffect(() => {
9
+ invoke<{ text: string }>('getText').then((t) => setText(t.text));
10
+ }, [invoke]);
11
+
12
+ return <div>{text}</div>;
13
+ }
@@ -0,0 +1 @@
1
+ export declare function HostRouterExample(): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { useHostRouter } from '@valiantys/atlassian-app-frontend';
2
+ import Button from '@atlaskit/button/new';
3
+ export function HostRouterExample() {
4
+ const hostRouter = useHostRouter();
5
+
6
+ return (
7
+ <div>
8
+ <Button onClick={() => void hostRouter.open('http://example.com/')}>
9
+ Navigate
10
+ </Button>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * This component invokes a resolver function on the Forge backend, which then invokes a Jira API.
3
+ */
4
+ export declare function IssueTypesExampleBackend(): import('react/jsx-runtime').JSX.Element;