Skip to content
This repository was archived by the owner on Feb 20, 2024. It is now read-only.

Commit f98d17a

Browse files
Merge pull request #49 from solace-iot-team/feat-uc-3
Feat uc 3
2 parents 295d4b6 + c6aca19 commit f98d17a

File tree

160 files changed

+2781
-4744
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

160 files changed

+2781
-4744
lines changed

.github/workflows/integration-test.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ on:
1818
branches:
1919
- main
2020
env:
21-
# DEBUG_FLAG: ${{ true }}
22-
DEBUG_FLAG: ${{ false }}
21+
DEBUG_FLAG: ${{ true }}
22+
# DEBUG_FLAG: ${{ false }}
2323
APIM_SERVER_DIR: "apim-server"
2424
APIM_SERVER_TEST_LOGS_DIR: "apim-server/test/logs"
2525
APIM_SERVER_TEST_LOGS_OUTPUT_NAME: "apim-server-test-logs"
@@ -68,6 +68,7 @@ jobs:
6868
echo "node --version: "; node --version
6969
echo "npm --version: "; npm --version
7070
echo "docker --version"; docker --version
71+
echo "docker-compose --version"; docker-compose --version
7172
7273
- name: "server: npm install"
7374
run: |

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
# Solace Async API Management
1212

13-
> :warning: UNDER CONSTRUCTION
14-
1513
Solace Async API Management.
1614

1715

ReleaseNotes.md

+48
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,54 @@
22

33
Solace Async API Management.
44

5+
## Version 0.3.6
6+
* [API-M Admin & Developer Portal](https://github.com/solace-iot-team/async-apim/tree/main/apim-portal): 0.3.6
7+
* [API-M Server OpenAPI](https://github.com/solace-iot-team/async-apim/blob/main/apim-server/server/common/api.yml): 0.3.2
8+
* [API-M Server](https://github.com/solace-iot-team/async-apim/tree/main/apim-server): 0.3.3
9+
* [API-M Connector OpenAPI](https://github.com/solace-iot-team/platform-api): 0.11.1
10+
11+
**New Features:**
12+
- **Download API asset zip file**
13+
- asset zip file download added to:
14+
- apis, api products, apps
15+
- **Organization Settings**
16+
- option to configure max number of environments per api product
17+
- display of asset version increment strategy: bump_patch (edit is disabled)
18+
19+
**Enhancements:**
20+
- **API Products: Policies: Guaranteed Messaging**
21+
- if disabled, deletes previous GM settings, mqtt bindings in API spec are set to qos=0 (drawback: previous settings are lost)
22+
- **Apps: Connection Endpoints**
23+
- for mqtt: added note: clientId can be found in Async API Spec (servers section)
24+
25+
**Changes:**
26+
- **APIs**
27+
- validation of Async API change to:
28+
- portal validates: title exists, version in semver format
29+
- connector validates everything else (including url $refs)
30+
- **API Products: new version number**
31+
- increment patch version on edit instead of minor version
32+
33+
**Fixes:**
34+
- **Organization Status**
35+
- restrict access to organization resources if organization connectivity is down (e.g. expired token)
36+
- **Active Connector**
37+
- fixed bug updating cache when updating active connector object
38+
39+
**APIM Server:**
40+
- **Connectors:Bootstrap**
41+
- fixed error handling and logging for non-existent connector bootstrap file & non-existent active connector
42+
- **Active Connector:Healthcheck**
43+
- improved error handling when connector is not healthy:
44+
- error: ConnectorProxyError, carries details of original error
45+
- logged and sent back to caller
46+
- added periodic healthcheck of active connector
47+
- if connector not healthy, log warning, code: "ACTIVE_CONNECTOR_TEST_ERROR"
48+
49+
**Releases:**
50+
- **APIM Portal**
51+
- changed to unprivileged nginx docker image: nginxinc/nginx-unprivileged:1.23.0
52+
553
## Version 0.3.5
654
* [API-M Admin & Developer Portal](https://github.com/solace-iot-team/async-apim/tree/main/apim-portal): 0.3.5
755
* [API-M Server OpenAPI](https://github.com/solace-iot-team/async-apim/blob/main/apim-server/server/common/api.yml): 0.3.1

apim-portal/package-lock.json

+15-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apim-portal/package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "async-apim-portal",
3-
"version": "0.3.5",
3+
"version": "0.3.6",
44
"description": "Solace Async API Management Portal",
55
"repository": {
66
"type": "git",
@@ -23,9 +23,8 @@
2323
"url": "https://github.com/solace-iot-team/async-apim/issues"
2424
},
2525
"dependencies": {
26-
"@asyncapi/parser": "^1.15.1",
2726
"@asyncapi/react-component": "1.0.0-next.33",
28-
"@solace-iot-team/apim-connector-openapi-browser": "^0.7.19",
27+
"@solace-iot-team/apim-connector-openapi-browser": "^0.11.1",
2928
"async-mutex": "^0.3.2",
3029
"base-64": "^1.0.0",
3130
"dompurify": "^2.3.8",

apim-portal/src/admin-portal/components/AdminPortalSideBar/AdminPortalSideBar.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import { PanelMenu } from 'primereact/panelmenu';
77
import { AuthContext } from "../../../components/AuthContextProvider/AuthContextProvider";
88
import { UserContext } from "../../../components/APContextProviders/APUserContextProvider";
99
import { APHealthCheckSummaryContext } from "../../../components/APHealthCheckSummaryContextProvider";
10+
import { OrganizationContext } from "../../../components/APContextProviders/APOrganizationContextProvider";
1011
import { EAPHealthCheckSuccess } from "../../../utils/APHealthCheck";
1112
import { AuthHelper } from "../../../auth/AuthHelper";
1213
import { EUIAdminPortalResourcePaths, EUICombinedResourcePaths, EUIDeveloperPortalResourcePaths } from '../../../utils/Globals';
14+
import { EAPOrganizationConfigStatus } from "../../../displayServices/APOrganizationsDisplayService/APOrganizationsDisplayService";
1315

1416
import '../../../components/APComponents.css';
1517

@@ -24,6 +26,7 @@ export const AdminPortalSideBar: React.FC<IAdminPortalSideBarProps> = (props: IA
2426
const [authContext] = React.useContext(AuthContext);
2527
const [userContext] = React.useContext(UserContext);
2628
const [healthCheckSummaryContext] = React.useContext(APHealthCheckSummaryContext);
29+
const [organizationContext] = React.useContext(OrganizationContext);
2730

2831
const navigateTo = (path: string): void => { history.push(path); }
2932

@@ -35,7 +38,8 @@ export const AdminPortalSideBar: React.FC<IAdminPortalSideBarProps> = (props: IA
3538
const isDisabledWithoutOrg = (resourcePath: EUICombinedResourcePaths): boolean => {
3639
return (
3740
!AuthHelper.isAuthorizedToAccessResource(authContext.authorizedResourcePathsAsString, resourcePath) ||
38-
!userContext.runtimeSettings.currentOrganizationEntityId
41+
!userContext.runtimeSettings.currentOrganizationEntityId ||
42+
organizationContext.apOrganizationConfigStatus !== EAPOrganizationConfigStatus.OPERATIONAL
3943
);
4044
}
4145
const isDisabledWithConnectorUnavailable = (isDisabledFunc: (resourcePath: EUICombinedResourcePaths) => boolean, resourcePath: EUICombinedResourcePaths): boolean => {

apim-portal/src/admin-portal/components/MaintainApiProducts/ListMaintainApiProducts.tsx

+14-7
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,20 @@ export const ListMaintainApiProducts: React.FC<IListMaintainApiProductsProps> =
175175
return row.apLifecycleStageInfo.stage;
176176
}
177177
const renderManagedObjectDataTable = () => {
178-
const dataKey = APAdminPortalApiProductsDisplayService.nameOf_ApEntityId('id');
179-
const sortField = APAdminPortalApiProductsDisplayService.nameOf_ApEntityId('displayName');
180-
const filterField = APAdminPortalApiProductsDisplayService.nameOf<TAPAdminPortalApiProductDisplay>('apSearchContent');
181-
// const approvalTypeSortField = APAdminPortalApiProductsDisplayService.nameOf<TAPAdminPortalApiProductDisplay>('apApprovalType');
182-
const accessLevelSortField = APAdminPortalApiProductsDisplayService.nameOf<TAPAdminPortalApiProductDisplay>('apAccessLevel');
183-
const stateSortField = APAdminPortalApiProductsDisplayService.nameOf_ApLifecycleStageInfo('stage');
184-
const businessGroupSortField = APAdminPortalApiProductsDisplayService.nameOf_ApBusinessGroupInfo_ApOwningBusinessGroupEntityId('displayName');
178+
// const dataKey = APAdminPortalApiProductsDisplayService.nameOf_ApEntityId('id');
179+
// const sortField = APAdminPortalApiProductsDisplayService.nameOf_ApEntityId('displayName');
180+
// const filterField = APAdminPortalApiProductsDisplayService.nameOf<TAPAdminPortalApiProductDisplay>('apSearchContent');
181+
// // const approvalTypeSortField = APAdminPortalApiProductsDisplayService.nameOf<TAPAdminPortalApiProductDisplay>('apApprovalType');
182+
// const accessLevelSortField = APAdminPortalApiProductsDisplayService.nameOf<TAPAdminPortalApiProductDisplay>('apAccessLevel');
183+
// const stateSortField = APAdminPortalApiProductsDisplayService.nameOf_ApLifecycleStageInfo('stage');
184+
// const businessGroupSortField = APAdminPortalApiProductsDisplayService.nameOf_ApBusinessGroupInfo_ApOwningBusinessGroupEntityId('displayName');
185+
const dataKey = APDisplayUtils.nameOf<TAPAdminPortalApiProductDisplay>('apEntityId.id');
186+
const sortField = APDisplayUtils.nameOf<TAPAdminPortalApiProductDisplay>('apEntityId.displayName');
187+
const filterField = APDisplayUtils.nameOf<TAPAdminPortalApiProductDisplay>('apSearchContent');
188+
const accessLevelSortField = APDisplayUtils.nameOf<TAPAdminPortalApiProductDisplay>('apAccessLevel');
189+
const stateSortField = APDisplayUtils.nameOf<TAPAdminPortalApiProductDisplay>('apLifecycleStageInfo.stage');
190+
const businessGroupSortField = APDisplayUtils.nameOf<TAPAdminPortalApiProductDisplay>('apBusinessGroupInfo.apOwningBusinessGroupEntityId.displayName');
191+
185192
return (
186193
<div className="card">
187194
<DataTable

apim-portal/src/admin-portal/components/MaintainApis/ListMaintainApis.tsx

+13-6
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,19 @@ export const ListMaintainApis: React.FC<IListMaintainApisProps> = (props: IListM
149149
return(<>{mo.apApiProductReferenceEntityIdList.length}</>);
150150
}
151151
const renderManagedObjectDataTable = () => {
152-
const dataKey = APApisDisplayService.nameOf_ApEntityId('id');
153-
const sortField = APApisDisplayService.nameOf_ApEntityId('displayName');
154-
const filterField = APApisDisplayService.nameOf<IAPApiDisplay>('apSearchContent');
155-
const stateSortField = APApisDisplayService.nameOf_ApLifecycleStageInfo('stage');
156-
const sourceSortField = APApisDisplayService.nameOf_ConnectorApiInfo('source');
157-
const businessGroupSortField = APApisDisplayService.nameOf_ApBusinessGroupInfo_ApOwningBusinessGroupEntityId('displayName');
152+
// const dataKey = APApisDisplayService.nameOf_ApEntityId('id');
153+
// const sortField = APApisDisplayService.nameOf_ApEntityId('displayName');
154+
// const filterField = APApisDisplayService.nameOf<IAPApiDisplay>('apSearchContent');
155+
// const stateSortField = APApisDisplayService.nameOf_ApLifecycleStageInfo('stage');
156+
// const sourceSortField = APApisDisplayService.nameOf_ConnectorApiInfo('source');
157+
// const businessGroupSortField = APApisDisplayService.nameOf_ApBusinessGroupInfo_ApOwningBusinessGroupEntityId('displayName');
158+
const dataKey = APDisplayUtils.nameOf<IAPApiDisplay>('apEntityId.id');
159+
const sortField = APDisplayUtils.nameOf<IAPApiDisplay>('apEntityId.displayName');
160+
const filterField = APDisplayUtils.nameOf<IAPApiDisplay>('apSearchContent');
161+
const stateSortField = APDisplayUtils.nameOf<IAPApiDisplay>('apLifecycleStageInfo.stage');
162+
const sourceSortField = APDisplayUtils.nameOf<IAPApiDisplay>('connectorApiInfo.source');
163+
const businessGroupSortField = APDisplayUtils.nameOf<IAPApiDisplay>('apBusinessGroupInfo.apOwningBusinessGroupEntityId.displayName');
164+
158165
return (
159166
<div className="card">
160167
<DataTable

apim-portal/src/admin-portal/components/ManageApiProducts/DisplayApiProduct.tsx

+49-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export const DisplayAdminPortalApiProduct: React.FC<IDisplayAdminPortalApiProduc
7373

7474
const [organizationContext] = React.useContext(OrganizationContext);
7575
const IsSingleApiSelection: boolean = organizationContext.apMaxNumApis_Per_ApiProduct === 1;
76+
const IsSingleEnvSelection: boolean = organizationContext.apMaxNumEnvs_Per_ApiProduct === 1;
7677
const ApiTabHeader: string = IsSingleApiSelection ? "API" : "API(s)";
78+
const EnvTabHeader: string = IsSingleEnvSelection ? "Environment" : "Environment(s)";
7779
const viewAppReferenceHistory = useHistory<TAPPageNavigationInfo>();
7880
const ReferencedByTabIndex: number = 5;
7981

@@ -134,6 +136,40 @@ export const DisplayAdminPortalApiProduct: React.FC<IDisplayAdminPortalApiProduc
134136
return callState;
135137
}
136138

139+
const apiGetApiZipContents = async(apiId: string, apiDisplayName: string): Promise<Blob | undefined> => {
140+
const funcName = 'apiGetApiZipContents';
141+
const logName = `${ComponentName}.${funcName}()`;
142+
if(managedObject === undefined) throw new Error(`${logName}: managedObject === undefined`);
143+
let callState: TApiCallState = ApiCallState.getInitialCallState(E_CALL_STATE_ACTIONS.API_GET_API_SPEC, `retrieve zip contents for api: ${apiDisplayName}`);
144+
let zipContents: Blob | undefined = undefined;
145+
try {
146+
switch(props.scope) {
147+
case E_DISPLAY_ADMIN_PORTAL_API_PRODUCT_SCOPE.VIEW_EXISTING:
148+
case E_DISPLAY_ADMIN_PORTAL_API_PRODUCT_SCOPE.VIEW_REFEREMCED_BY:
149+
case E_DISPLAY_ADMIN_PORTAL_API_PRODUCT_SCOPE.VIEW_EXISTING_MAINTAIN:
150+
zipContents = await APApiSpecsDisplayService.apiGet_ApiProduct_ApiSpec_ZipContents({
151+
organizationId: props.organizationId,
152+
apiProductId: managedObject.apEntityId.id,
153+
apiEntityId: { id: apiId, displayName: apiDisplayName }
154+
});
155+
break;
156+
case E_DISPLAY_ADMIN_PORTAL_API_PRODUCT_SCOPE.REVIEW_AND_CREATE:
157+
zipContents = await APApiSpecsDisplayService.apiGet_Api_ApiSpec_ZipContents({
158+
organizationId: props.organizationId,
159+
apiEntityId: { id: apiId, displayName: apiDisplayName }
160+
});
161+
break;
162+
default:
163+
Globals.assertNever(logName, props.scope);
164+
}
165+
} catch(e) {
166+
APClientConnectorOpenApi.logError(logName, e);
167+
callState = ApiCallState.addErrorToApiCallState(e, callState);
168+
}
169+
setApiCallStatus(callState);
170+
return zipContents;
171+
}
172+
137173
const doInitialize = async () => {
138174
setManagedObject(props.apAdminPortalApiProductDisplay);
139175
}
@@ -182,6 +218,16 @@ export const DisplayAdminPortalApiProduct: React.FC<IDisplayAdminPortalApiProduc
182218
props.onLoadingChange(false);
183219
}
184220

221+
const doFetchZipContents = async(): Promise<Blob | undefined> => {
222+
const funcName = 'doFetchZipContents';
223+
const logName = `${ComponentName}.${funcName}()`;
224+
if(showApiId === undefined) throw new Error(`${logName}: showApiId === undefined`);
225+
props.onLoadingChange(true);
226+
const zipContents: Blob | undefined = await apiGetApiZipContents(showApiId, showApiId);
227+
props.onLoadingChange(false);
228+
if(zipContents !== undefined) return zipContents;
229+
}
230+
185231
React.useEffect(() => {
186232
if(showApiId === undefined) return;
187233
doFetchApiSpec(showApiId);
@@ -417,6 +463,7 @@ export const DisplayAdminPortalApiProduct: React.FC<IDisplayAdminPortalApiProduc
417463
schemaId={showApiId}
418464
onDownloadSuccess={props.onSuccess}
419465
onDownloadError={props.onError}
466+
fetchZipContentsFunc={doFetchZipContents}
420467
/>
421468
</React.Fragment>
422469
}
@@ -436,9 +483,9 @@ export const DisplayAdminPortalApiProduct: React.FC<IDisplayAdminPortalApiProduc
436483
</TabPanel>
437484
);
438485
tabPanels.push(
439-
<TabPanel header='Environments'>
486+
<TabPanel header={EnvTabHeader}>
440487
<React.Fragment>
441-
<div className="p-text-bold">Environments:</div>
488+
<div className="p-text-bold">{EnvTabHeader}:</div>
442489
<div className="p-ml-2">
443490
{APEntityIdsService.create_SortedDisplayNameList_From_ApDisplayObjectList(managedObject.apEnvironmentDisplayList).join(', ')}
444491
</div>

apim-portal/src/admin-portal/components/ManageApiProducts/EditNewApiProduct/EditAccessAndState.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,17 @@ export const EditAccessAndState: React.FC<IEditAccessAndStateProps> = (props: IE
6363
const logName = `${ComponentName}.${funcName}()`;
6464
let callState: TApiCallState = ApiCallState.getInitialCallState(E_CALL_STATE_ACTIONS.API_UPDATE_API_PRODUCT, `update api product: ${mo.apEntityId.displayName}`);
6565
try {
66-
await APAdminPortalApiProductsDisplayService.apiUpdate_ApApiProductDisplay({
66+
await APAdminPortalApiProductsDisplayService.apiUpdate_ApApiProductDisplay_AccessAndState({
6767
organizationId: props.organizationId,
68-
apApiProductDisplay: APAdminPortalApiProductsDisplayService.set_ApApiProductDisplay_AccessAndState({
69-
apApiProductDisplay: props.apAdminPortalApiProductDisplay,
70-
apApiProductDisplay_AccessAndState: mo
71-
}),
7268
userId: userContext.apLoginUserDisplay.apEntityId.id,
69+
apApiProductDisplay: props.apAdminPortalApiProductDisplay,
70+
apApiProductDisplay_AccessAndState: mo
7371
});
72+
// TODO: set the new values - required?
73+
// apApiProductDisplay: APAdminPortalApiProductsDisplayService.set_ApApiProductDisplay_AccessAndState({
74+
// apApiProductDisplay: props.apAdminPortalApiProductDisplay,
75+
// apApiProductDisplay_AccessAndState: mo
76+
// }),
7477
} catch(e: any) {
7578
APSClientOpenApi.logError(logName, e);
7679
callState = ApiCallState.addErrorToApiCallState(e, callState);

0 commit comments

Comments
 (0)