chore(ui): add dirty check loop to devportal MD editor
MODIFY
common/util/src/main/java/io/apiman/common/util/JsonUtil.java
common/util/src/main/java/io/apiman/common/util/JsonUtil.java
@@ -28,6 +28,9 @@ import static com.fasterxml.jackson.core.json.JsonReadFeature.ALLOW_UNQUOTED_FIE
* @author Marc Savy {@literal <marc@blackparrotlabs.io>}
*/
public class JsonUtil {
+ public JsonUtil() {
+ }
+
private static final ObjectMapper OM = JsonMapper
.builder()
// (mostly) match the JSON5 spec.

MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/blobstore/BlobStoreRepository.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/blobstore/BlobStoreRepository.java
@@ -101,7 +101,7 @@ public class BlobStoreRepository extends AbstractJpaStorage {
getActiveEntityManager()
.createQuery("DELETE FROM BlobEntity b "
+ "WHERE b.createdOn < :timeThreshold "
- + "AND b.references = 0")
+ + "AND b.references <= 0")
.setParameter("timeThreshold", timeThreshold)
.executeUpdate();
}

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/impl/OrganizationResourceImpl.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/impl/OrganizationResourceImpl.java
@@ -128,6 +128,7 @@ import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
+import javax.transaction.Transactional;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -143,6 +144,7 @@ import org.apache.commons.lang3.BooleanUtils;
* @author eric.wittmann@redhat.com
*/
@RequestScoped
+@Transactional
public class OrganizationResourceImpl implements IOrganizationResource, DataAccessUtilMixin {
private static final IApimanLogger LOGGER = ApimanLoggerFactory.getLogger(OrganizationResourceImpl.class);

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/service/NotificationService.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/service/NotificationService.java
@@ -127,36 +127,6 @@ public class NotificationService implements DataAccessUtilMixin {
return new SearchResultsBean<NotificationDto<?>>(dtos, results.getTotalSize());
}
- // public void sendNotification(@NotNull CreateNotificationDto newNotification, String organization) {
- // LOGGER.debug("Creating new notification(s): {0}", newNotification);
- //
- // List<UserDto> resolvedRecipients = calculateRecipients(newNotification.getRecipient(), organization);
- //
- // for (UserDto resolvedRecipient : resolvedRecipients) {
- // NotificationEntity notificationEntity = new NotificationEntity()
- // .setCategory(newNotification.getCategory())
- // .setReason(newNotification.getReason())
- // .setReasonMessage(newNotification.getReasonMessage())
- // .setNotificationStatus(NotificationStatus.OPEN)
- // .setRecipient(resolvedRecipient.getUsername())
- // .setSource(newNotification.getSource())
- // .setPayload(JsonUtil.toJsonTree(newNotification.getPayload()));
- //
- // tryAction(() -> {
- // // 1. Save notification into notifications table.
- // LOGGER.trace("Creating notification entity in repository layer: {0}", notificationEntity);
- // notificationRepository.create(notificationEntity);
- //
- // // Avoiding serializing and deserializing the payload immediately!
- // NotificationDto<?> dto = toDto(notificationEntity, newNotification.getPayload(), resolvedRecipient);
- //
- // // 2. Emit notification onto notification bus.
- // LOGGER.trace("Firing notification: {0}", dto);
- // notificationDispatcher.fire(dto);
- // });
- // }
- // }
-
/**
* Send a new notification to a specified recipient (userId)
*

MODIFY
manager/api/war/wildfly8/pom.xml
manager/api/war/wildfly8/pom.xml
<classifier>classes</classifier>
<scope>provided</scope>
</dependency>
-
<!-- Project dependencies not included in the base WAR -->
<dependency>
<groupId>io.apiman</groupId>
<groupId>io.apiman</groupId>
<artifactId>apiman-common-logging-jboss</artifactId>
</dependency>
-
- <!-- Project dependencies in the base WAR but that are also directly used -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ </dependency>
+ <!-- Use our own version of Jackson --><!-- Project dependencies in the base WAR but that are also directly used -->
<dependency>
<groupId>io.apiman</groupId>
<artifactId>apiman-manager-api-core</artifactId>

MODIFY
manager/api/war/wildfly8/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
manager/api/war/wildfly8/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
<module name="org.hibernate.commons-annotations" />
<module name="org.jboss.logging" services="import"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
-<!-- <module name="org.jboss.weld.core" services="import"/>-->
-<!-- <module name="org.jboss.weld.spi" services="import"/>-->
</dependencies>
<exclude-subsystems>
<subsystem name="webservices" />
</exclude-subsystems>
<exclusions>
+ <module name="com.fasterxml.jackson.core.jackson-core" />
+ <module name="com.fasterxml.jackson.core.jackson-annotations" />
+ <module name="com.fasterxml.jackson.core.jackson-databind" />
+ <module name="com.fasterxml.jackson.datatype.jackson-datatype-jdk8" />
+ <module name="com.fasterxml.jackson.datatype.jackson-datatype-jsr310" />
+ <module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider" />
+ <module name="org.jboss.resteasy.resteasy-jackson2-provider" />
+ <module name="org.jboss.resteasy.resteasy-jackson-provider" />
<module name="org.jboss.resteasy.resteasy-jackson-provider" />
</exclusions>
</deployment>

MODIFY
manager/ui/war/plugins/api-manager/ts/api/api-devportal.ts
manager/ui/war/plugins/api-manager/ts/api/api-devportal.ts
@@ -9,16 +9,16 @@ module Apiman {
import ApiVersionBean = ApimanRPC.ApiVersionBean;
import BlobRef = ApimanRPC.BlobRef;
- export var ApiDevPortalController = _module.controller( "Apiman.DevPortalController",
- ['$q', '$scope', '$location', 'PageLifecycle', 'ApiEntityLoader', 'OrgSvcs', 'ApimanSvcs', '$routeParams', 'Configuration', 'EntityStatusSvc', 'DevPortalService', 'BlobService', 'Logger', '$window',
- ($q, $scope, $location, PageLifecycle, ApiEntityLoader, OrgSvcs, ApimanSvcs, $routeParams, Configuration, EntityStatusSvc, DevPortalService, BlobService, Logger, $window) => {
+ export var ApiDevPortalController = _module.controller( 'Apiman.DevPortalController',
+ ['$q', '$scope', '$rootScope', '$location', 'PageLifecycle', 'ApiEntityLoader', 'OrgSvcs', 'ApimanSvcs', '$routeParams', 'Configuration', 'EntityStatusSvc', 'DevPortalService', 'BlobService', 'Logger', '$window', '$interval',
+ ($q, $scope, $rootScope, $location, PageLifecycle, ApiEntityLoader, OrgSvcs, ApimanSvcs, $routeParams, Configuration, EntityStatusSvc, DevPortalService, BlobService, Logger, $window, $interval) => {
const params = $routeParams;
$scope.organizationId = params.org;
$scope.tab = 'devportal';
$scope.version = params.version;
$scope.showMetrics = Configuration.ui.metrics;
$scope.isEntityDisabled = EntityStatusSvc.isEntityDisabled;
- $scope.isDirty = false;
+ $rootScope.isDirty = false;
$scope.apiVersion = null as ApiVersionBean;
let apiVersionCopy = null as ApiVersionBean;
$scope.planSummaries = [] as ApiPlanSummaryBean[];
@@ -42,6 +42,7 @@ module Apiman {
);
}
+ /*** Markdown Editor ***/
// Load Markdown Editor
const ed = new $window.editor({
el: document.querySelector('#editor'),
@@ -52,9 +53,28 @@ module Apiman {
hooks: {
addImageBlobHook: uploadImage
},
+ events: {
+ blur: () => editorDirtyCheck
+ },
plugins: [[$window.codeSyntaxHighlightPlugin, { highlighter: $window.prism }]]
});
+ // Dirty check the MD editor pane every 2 seconds (avoids excessive checking).
+ $interval(() => {
+ editorDirtyCheck();
+ }, 1000);
+
+ const editorDirtyCheck = (): void => {
+ let latestDescription = ed.getMarkdown();
+ // Original description != latestDescription
+ if (apiVersionCopy.extendedDescription !== latestDescription) {
+ $rootScope.isDirty = true;
+ $scope.apiVersion.extendedDescription = latestDescription;
+ }
+ }
+
+ /*** End Markdown Editor ***/
+
// Get the API Version Plan summaries
DevPortalService.getApiVersionPlans(params.org, params.api, params.version).then(
(apiPlans: ApiPlanSummaryBean[]) => {
@@ -82,7 +102,7 @@ module Apiman {
return;
}
// Logger.debug("Dirty set to true {0} vs {1}", oldValue, newValue);
- $scope.isDirty = true;
+ $rootScope.isDirty = true;
}, true);
},
(failure) => handleFailure(failure)
@@ -92,7 +112,8 @@ module Apiman {
// Reset (copy saved pristine copy back over).
$scope.reset = () => {
$scope.apiVersion = angular.copy(apiVersionCopy) as ApiVersionBean;
- $scope.isDirty = false;
+ ed.setMarkdown($scope.apiVersion.extendedDescription, true);
+ $rootScope.isDirty = false;
};
// Save
@@ -105,15 +126,15 @@ module Apiman {
} as UpdateApiVersionBean;
DevPortalService.updateApiVersion(params.org, params.api, params.version, updateApiVersionBean).then(
- (ok) => {
+ (_) => {
Logger.info("Api Version update succeeded!");
- $scope.isDirty = false;
+ $rootScope.isDirty = false;
},
(failure) => handleFailure(failure)
);
}
- function handleFailure(failure: any) {
+ function handleFailure(failure: any): void {
// TODO(msavy): do something useful here...
Logger.error("failure {0}", failure.data || failure);
}

MODIFY
manager/ui/war/plugins/api-manager/ts/client/client-contracts.ts
manager/ui/war/plugins/api-manager/ts/client/client-contracts.ts
/// <reference path="../apimanPlugin.ts"/>
/// <reference path="../rpc.ts"/>
-module Apiman {
+/// <reference path="../model/contract.d.ts"/>
+
+import {ContractSummaryBean} from "../model/contract";
+module Apiman {
export var ClientContractsController = _module.controller('Apiman.ClientContractsController',
[
'$q',
@@ -27,7 +30,7 @@ module Apiman {
pageData = angular.extend(pageData, {
contracts: $q(function(resolve, reject) {
OrgSvcs.query({ organizationId: params.org, entityType: 'clients', entityId: params.client, versionsOrActivity: 'versions', version: params.version, policiesOrActivity: 'contracts' }, function(contracts) {
- $scope.filteredContracts = contracts;
+ $scope.filteredContracts = contracts as ContractSummaryBean[];
resolve(contracts);
}, reject);
})

MODIFY
manager/ui/war/plugins/api-manager/ts/directives.ts
manager/ui/war/plugins/api-manager/ts/directives.ts
@@ -228,7 +228,7 @@ module Apiman {
$(element).removeClass();
$(element).addClass('apiman-label');
- if (entityStatus == 'Created' || entityStatus == 'Ready') {
+ if (entityStatus == 'Created' || entityStatus == 'Ready' || entityStatus == 'AwaitingApproval') {
$(element).addClass('apiman-label-warning');
} else if (entityStatus == 'Retired') {
$(element).addClass('apiman-label-default');

ADD
manager/ui/war/plugins/api-manager/ts/model/contract.d.ts
+ export interface ContractSummaryBean {
+ contractId: number;
+ clientOrganizationId: string;
+ clientOrganizationName: string;
+ clientId: string;
+ clientName: string;
+ clientVersion: string;
+ apiOrganizationId: string;
+ apiOrganizationName: string;
+ apiId: string;
+ apiName: string;
+ apiVersion: string;
+ apiDescription: string;
+ planName: string;
+ planId: string;
+ planVersion: string;
+ createdOn: Date;
+ status: string; // ContractStatus enum
+ }

chore(ui): add dirty check loop to devportal MD editor