@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.
- package/README.md +726 -0
- package/atlassian-app-forge-CTaVjJLt.js +1 -0
- package/atlassian-app-forge-DdtDadi2.mjs +117 -0
- package/atlassian-app-frontend.api.json +8829 -0
- package/atlassian-app-frontend.api.md +740 -0
- package/atlassian-app-standalone-DJOVbfp6.js +1 -0
- package/atlassian-app-standalone-DyyH6WPO.mjs +111 -0
- package/examples/backend/index.ts +44 -0
- package/examples/backend/lib/forge-functions.d.ts +3 -0
- package/examples/backend/lib/forge-functions.ts +23 -0
- package/examples/backend/lib/handler-functions.d.ts +58 -0
- package/examples/backend/lib/handler-functions.ts +72 -0
- package/examples/backend/lib/standalone-functions.d.ts +3 -0
- package/examples/backend/lib/standalone-functions.ts +17 -0
- package/examples/client-sample-.env +5 -0
- package/examples/hello-world/app.d.ts +2 -0
- package/examples/hello-world/app.tsx +77 -0
- package/examples/hello-world/example-box.d.ts +8 -0
- package/examples/hello-world/example-box.tsx +22 -0
- package/examples/hello-world/example-components/forge-storage-example.d.ts +1 -0
- package/examples/hello-world/example-components/forge-storage-example.tsx +66 -0
- package/examples/hello-world/example-components/hello-with-loading-spinner.d.ts +1 -0
- package/examples/hello-world/example-components/hello-with-loading-spinner.tsx +24 -0
- package/examples/hello-world/example-components/hello.d.ts +1 -0
- package/examples/hello-world/example-components/hello.spec.tsx +39 -0
- package/examples/hello-world/example-components/hello.tsx +13 -0
- package/examples/hello-world/example-components/host-router-example.d.ts +1 -0
- package/examples/hello-world/example-components/host-router-example.tsx +13 -0
- package/examples/hello-world/example-components/issue-types-example-backend.d.ts +4 -0
- package/examples/hello-world/example-components/issue-types-example-backend.tsx +37 -0
- package/examples/hello-world/example-components/issue-types-example.d.ts +4 -0
- package/examples/hello-world/example-components/issue-types-example.tsx +40 -0
- package/examples/hello-world/example-components/list-assets.d.ts +1 -0
- package/examples/hello-world/example-components/list-assets.tsx +38 -0
- package/examples/hello-world/example-components/open-modal-example.d.ts +1 -0
- package/examples/hello-world/example-components/open-modal-example.tsx +47 -0
- package/examples/hello-world/example-components/view-context-example.d.ts +1 -0
- package/examples/hello-world/example-components/view-context-example.tsx +17 -0
- package/examples/hello-world/example-components/who-am-i.d.ts +1 -0
- package/examples/hello-world/example-components/who-am-i.tsx +13 -0
- package/examples/hello-world/main.d.ts +0 -0
- package/examples/hello-world/main.tsx +11 -0
- package/examples/hello-world/styles.css +1 -0
- package/examples/hello-world-modal/app.d.ts +2 -0
- package/examples/hello-world-modal/app.tsx +30 -0
- package/examples/hello-world-modal/hello.d.ts +1 -0
- package/examples/hello-world-modal/hello.tsx +15 -0
- package/examples/hello-world-modal/main.d.ts +0 -0
- package/examples/hello-world-modal/main.tsx +11 -0
- package/examples/hello-world-modal/styles.css +1 -0
- package/examples/hello-world-remote/app.d.ts +2 -0
- package/examples/hello-world-remote/app.tsx +23 -0
- package/examples/hello-world-remote/invoke-remote-example.d.ts +13 -0
- package/examples/hello-world-remote/invoke-remote-example.tsx +40 -0
- package/examples/hello-world-remote/main.d.ts +0 -0
- package/examples/hello-world-remote/main.tsx +11 -0
- package/examples/hello-world-remote/styles.css +1 -0
- package/examples/manifest.yml.example +49 -0
- package/index-CBKhl1FP.mjs +22 -0
- package/index-CP8emE0q.js +1 -0
- package/index.d.ts +654 -0
- package/index.js +2 -0
- package/index.mjs +1145 -0
- package/package.json +54 -0
- package/style.css +1 -0
- 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,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,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,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,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
|
+
}
|