strong-arc
Version:
A visual suite for the StrongLoop API Platform
724 lines (644 loc) • 25.9 kB
JavaScript
Composer.controller('ComposerMainController', [
'$rootScope',
'$q',
'$log',
'$scope',
'$state',
'$http',
'IAService',
'DataSourceService',
'WorkspaceServices',
'DiscoveryService',
'PropertyService',
'$location',
'$timeout',
'ModelService',
'growl',
function($rootScope, $q, $log, $scope, $state, $http, IAService, DataSourceService, WorkspaceServices, DiscoveryService, PropertyService, $location, $timeout, ModelService, growl) {
// Instance Collections
$scope.mainNavDatasources = []; // initialized further down
$scope.mainNavModels = []; // initialized further down
$scope.instanceType = 'model';
$scope.activeInstance = {};
$scope.modelNavIsVisible = true;
$scope.dsNavIsVisible = true;
$scope.globalExceptionStack = [];
$scope.datasource = {
connectionTestResponse: '',
connectionTestResponseType: 'success'
};
// list of open instances (models/datasources)
$scope.openInstanceRefs = IAService.getOpenInstanceRefs();
// legacy
$scope.currentOpenDatasourceNames = IAService.getOpenDatasourceNames();
$scope.currentSelectedCollection = IAService.clearInstanceSelections();
// dirty flags
$scope.apiModelsChanged = false; // dirty flag
$scope.apiDataSourcesChanged = false;
$scope.activeModelPropertiesChanged = false; // dirty flag
$scope.activeInstanceUpdated = false; // dirty toggle for active instance update
// initialize UI state
// set active instance if available
$rootScope.$watch('projectName', function(name) {
if (name) {
// initialize active instance
$scope.activeInstance = IAService.getActiveInstance();
if ($scope.activeInstance) {
// refresh the instance to avloid stale data between server/client
IAService.activateInstanceById($scope.activeInstance.id, $scope.activeInstance.type)
.then(function(response) {
$scope.activeInstance = response;
$rootScope.$broadcast('IANavEvent');
});
}
}
});
// Validate the workspace
WorkspaceServices.validate().then(function(isValid) {
if (!isValid) {
$rootScope.$broadcast('GlobalExceptionEvent', {
isFatal: true,
message: WorkspaceServices.validationError.message,
code: WorkspaceServices.validationError.code,
details: 'API Composer only works with valid LoopBack projects',
help: [
{ text: 'Ensure you have LoopBack installed and create your project using the slc loopback command.' },
{ text: 'See:' },
{ link: 'http://loopback.io/',
text: 'LoopBack getting started guide' },
{ text: 'for more information' }
]
}
);
}
});
// Load Model and DataSource collections
var loadModels = function() {
$scope.mainNavModels = ModelService.getAllModelInstances();
$scope.mainNavModels.
then(function (result) {
$scope.mainNavModels = result;
$scope.apiModelsChanged = !$scope.apiModelsChanged;
$rootScope.$broadcast('IANavEvent');
}
);
};
loadModels();
var loadDataSources = function() {
$scope.mainNavDatasources = DataSourceService.getAllDataSourceInstances();
$scope.mainNavDatasources.
then(function (result) {
$scope.mainNavDatasources = result;
$scope.apiDataSourcesChanged = !$scope.apiDataSourcesChanged;
$rootScope.$broadcast('IANavEvent');
});
};
loadDataSources();
// Helper methods
$scope.clearSelectedInstances = function() {
$scope.instanceSelections = IAService.clearInstanceSelections();
};
$scope.clearSelectedDatasources = function() {
$scope.currentDatasourceSelections = IAService.clearSelectedDatasourceNames();
};
$scope.setApiModelsDirty = function() {
$scope.apiModelsChanged = !$scope.apiModelsChanged;
};
$scope.$watchGroup(['activeInstance', 'openInstanceRefs'],
function(newVal) {
if (!Array.isArray($scope.openInstanceRefs)) {
return;
}
$scope.tabItems = $scope.openInstanceRefs.map(
function(openInstance) {
return angular.extend({}, openInstance, {
isActive: openInstance.name === $scope.activeInstance.name
});
}
);
}
);
$scope.openSelectedInstance = function(id, type) {
if (id && type) {
IAService.activateInstanceById(id, type).
then(function(instance) {
$scope.openInstanceRefs = IAService.getOpenInstanceRefs();
$scope.activeInstance = instance;
$rootScope.$broadcast('IANavEvent');
}).
catch(function(response) {
$log.warn('problem activating instance: ' + '[' + id + ']' + response);
}
);
}
};
// delete instance
$scope.deleteInstanceRequest = function(instanceIdConfig, type) {
if (instanceIdConfig){
var confirmText = 'delete model?';
if (type === CONST.DATASOURCE_TYPE){
confirmText = 'delete data source?';
}
if (confirm(confirmText)){
if (type === CONST.MODEL_TYPE) {
// delete the model
ModelService.deleteModelInstance(instanceIdConfig.definitionId, instanceIdConfig.configId).
then(function(response){
// remove from open instance refs
$scope.activeInstance = IAService.resetActiveToFirstOpenInstance(instanceIdConfig.definitionId);
loadModels();
}
);
}
else if (type === CONST.DATASOURCE_TYPE) {
DataSourceService.deleteDataSourceInstance(instanceIdConfig.definitionId).
then(function(response){
$scope.activeInstance = IAService.resetActiveToFirstOpenInstance(instanceIdConfig.definitionId);
//reset data source for models using the deleted datasource
var models = $scope.mainNavModels.filter(function(model){
var dataSourceId = instanceIdConfig.definitionId.split('.')[1];
return model.config.dataSource === dataSourceId;
});
models.forEach(function(model){
model.config.dataSource = null;
//save the model
ModelService.updateModelInstance(model);
});
loadModels();
loadDataSources();
}
);
}
}
}
};
// create new instance
$scope.createNewInstance = function(type, initialData) {
// start New Model
if (!type) {
$log.warn('createNewInstance called with no type argument');
return;
}
var newDefaultInstance = {};
if (type === CONST.MODEL_TYPE) {
// check if new model is already open
if (IAService.isNewModelOpen()) {
// easier to just close it an refresh than to activate existing instance ref
IAService.closeInstanceById(CONST.NEW_MODEL_PRE_ID);
}
newDefaultInstance = ModelService.createNewModelInstance(initialData);
}
else if (type === CONST.DATASOURCE_TYPE) {
if (IAService.isNewDataSourceOpen()) {
IAService.closeInstanceById(CONST.NEW_DATASOURCE_PRE_ID);
}
newDefaultInstance = DataSourceService.createNewDataSourceInstance(initialData);
}
$scope.activeInstance = IAService.setActiveInstance(newDefaultInstance);
IAService.addInstanceRef($scope.activeInstance);
$scope.openInstanceRefs = IAService.getOpenInstanceRefs();
$scope.clearSelectedInstances();
$rootScope.$broadcast('IANavEvent');
};
// update active instance
$scope.updateActiveInstance = function(instance, type, id) {
$scope.activeInstance = IAService.setActiveInstance(instance, type, id);
$scope.openInstanceRefs = IAService.getOpenInstanceRefs();
};
// nav branch clicked
$scope.navTreeBranchClicked = function(type) {
$scope.clearSelectedInstances();
};
// nav tree item clicked
$scope.navTreeItemClicked = function(type, targetId, multiSelect) {
$scope.openSelectedInstance(targetId, type);
};
// editor tab clicked
$scope.instanceTabItemClicked = function(id) {
var openInstanceRefs = IAService.getOpenInstanceRefs();
// defensive check to make sure the component is initialized
if (openInstanceRefs && openInstanceRefs.length > 0){
// only if the model isn't currently active
if ($scope.activeInstance.id !== id) {
var type;
// get the type of instance
for (var i = 0;i < openInstanceRefs.length;i++) {
if (openInstanceRefs[i].id === id) {
type = openInstanceRefs[i].type;
break;
}
}
if (type) {
IAService.activateInstanceById(id, type).
then(function(instance) {
$scope.activeInstance = instance;
$scope.clearSelectedInstances();
}).
catch(function(error) {
console.warn('problem activating instance: ' + error);
});
}
else {
$log.warn('unable to get type from open instance ref: ' + id);
}
}
}
};
// close editor tab
$scope.instanceTabItemCloseClicked = function(id) {
$scope.openInstanceRefs = IAService.closeInstanceById(id);
// reset the active instance and reset tabs and nav
if ($scope.activeInstance.id === id) {
if ($scope.openInstanceRefs.length === 0) {
IAService.clearActiveInstance();
$scope.activeInstance = IAService.setActiveInstance({});
$rootScope.$broadcast('IANavEvent');
} else {
// active the first instance by default
IAService.activateInstanceById($scope.openInstanceRefs[0].id, $scope.openInstanceRefs[0].type).
then(function(instance) {
$scope.activeInstance = instance;
$rootScope.$broadcast('IANavEvent');
});
}
}
$scope.clearSelectedInstances();
};
$scope.updateActiveInstanceName = function(name) {
$scope.activeInstance.name = name;
$scope.activeInstanceUpdated = !$scope.activeInstanceUpdated;
};
// flag to prevent 'double' saves and race conditions during intial creation
$scope.isCreatingModelDef = false;
// save model
$scope.saveModelInstanceRequest = function(instance) {
var originalModelId = instance.definition.id;
if (instance.id && (instance.id !== CONST.NEW_MODEL_PRE_ID)) {
// update model
if (!$scope.isCreatingModelDef) { // test to ensure not in init create cycle still
ModelService.updateModelInstance(instance).
then(function(instance) {
growl.addSuccessMessage("model saved");
$scope.activeInstance = instance;
IAService.updateOpenInstanceRef(originalModelId, $scope.activeInstance.type, $scope.activeInstance);
$scope.updateActiveInstance(
$scope.activeInstance, CONST.DATASOURCE_TYPE, originalModelId);
loadModels();
}).
catch(function(error) {
$log.warn('bad update model definition: ' + error);
});
}
}
else {
// double check to clear out 'new' id
if (instance.definition.id === CONST.NEW_MODEL_PRE_ID) {
delete instance.id;
delete instance.definition.id;
}
if (!$scope.isCreatingModelDef) {
$scope.isCreatingModelDef = true;
// create model
ModelService.createModelInstance(instance).
then(function(instanceResponse) {
growl.addSuccessMessage("model created");
$scope.activeInstance = instanceResponse;
$scope.isCreatingModelDef = false; // toggle off creating flag
IAService.updateOpenInstanceRef(originalModelId, $scope.activeInstance.type, $scope.activeInstance);
$scope.updateActiveInstance(
$scope.activeInstance, CONST.MODEL_TYPE, originalModelId);
loadModels();
$rootScope.$broadcast('IANavEvent');
})
.catch(function(error) {
$scope.isCreatingModelDef = false; // toggle off creating flag
$log.error('bad create model instance ' + error.message);
});
}
}
};
$scope.createModelViewRequest = function() {
$scope.instanceType = CONST.MODEL_TYPE;
var isNewOpen = IAService.isNewModelOpen();
// check to see if new mode is already open
if (isNewOpen) {
// if it is check if it is active
// if new model is open but not active then just close it and
// start a fresh one
// the downside of this is that any unsaved data will be lost
// unfortunately the way the open tabs are handled the data isn't there anyway
// so will have to enhance in the future to preserve unsaved and inactive data
// TODO sean
if ($scope.activeInstance && ($scope.activeInstance.id !== CONST.NEW_MODEL_PRE_ID)) {
$scope.openInstanceRefs = IAService.closeInstanceById(CONST.NEW_MODEL_PRE_ID);
$scope.createNewInstance(CONST.MODEL_TYPE);
}
}
else {
$scope.createNewInstance(CONST.MODEL_TYPE);
}
};
$scope.migrateModelConfig = function(config) {
var instance = $scope.activeInstance;
instance.isMigrating = true;
return ModelService.migrateModelConfig(config)
.then(function() {
instance.isMigrating = false;
}, function(ex) {
growl.addErrorMessage(
'could not migrate model (check console for details)'
);
})
.then(function() {
growl.addSuccessMessage('model migrated');
});
}
// update model property
$scope.updateModelPropertyRequest = function(config) {
var modelPropertyConfig = config.propertyConfig;
if (modelPropertyConfig && modelPropertyConfig.modelId && modelPropertyConfig.id && modelPropertyConfig.name) {
var propertyNameSnapshot = getPropertyNameSnapshot(config.currProperties);
var currentIndex = getPropertyIndex(modelPropertyConfig.name, propertyNameSnapshot);
PropertyService.updateModelProperty(modelPropertyConfig)
.then(function(updatedProperty) {
growl.addSuccessMessage('property updated');
$scope.activeInstance.properties[currentIndex] = updatedProperty;
IAService.setActiveInstance($scope.activeInstance);
$scope.activeModelPropertiesChanged = !$scope.activeModelPropertiesChanged;
});
}
else {
$log.warn('update property called with insufficient parameters: ' + JSON.stringify(modelPropertyConfig));
}
};
function getPropertyNameSnapshot(propArray) {
var retArray = [];
propArray.map(function(item) {
retArray.push(item.name);
});
return retArray;
}
function getPropertyIndex(name, list) {
for (var i = 0; i < list.length; i++) {
if (list[i] === name) {
return i;
}
}
}
// update model property
$scope.updateModelPropertyNameRequest = function(config) {
var modelPropertyConfig = config.propertyConfig;
if (modelPropertyConfig && modelPropertyConfig.modelId && modelPropertyConfig.name) {
var propertyNameSnapshot = getPropertyNameSnapshot(config.currProperties);
var currentIndex = getPropertyIndex(modelPropertyConfig.name, propertyNameSnapshot);
PropertyService.updateModelProperty(modelPropertyConfig)
.then(function(updatedProperty) {
growl.addSuccessMessage('property updated');
$scope.activeInstance.properties[currentIndex] = updatedProperty;
IAService.setActiveInstance($scope.activeInstance);
$scope.activeModelPropertiesChanged = !$scope.activeModelPropertiesChanged;
});
}
};
// delete model property
$scope.deleteModelPropertyRequest = function(config) {
if (config.id && config.modelId) {
if (confirm('delete this model property?')){
// this seems a bit redundant and could likely benefit
// from refactoring
// delete the property from the active instance and then reload the
// whole instance again is 2 async calls
PropertyService.deleteModelProperty(config).
then(function(response) {
ModelService.getModelInstanceById(config.modelId).
then(function(instance) {
growl.addSuccessMessage("property deleted");
$scope.activeInstance = IAService.setActiveInstance(instance, CONST.MODEL_TYPE);
$scope.activeModelPropertiesChanged = !$scope.activeModelPropertiesChanged; }
);
}
);
}
}
};
// create model property
$scope.createNewModelProperty = function() {
// make sure the model has finished initializing
if ($scope.isCreatingModelDef) {
return $timeout(function() {
$scope.createNewModelProperty();
}, 35);
}
// get model id
var modelId = $scope.activeInstance.id;
// should get a config (with at least name/type of property)
if (modelId) {
var propConfig = {
name:'propertyName' + getRandomNumber(),
type: 'string',
facetName: CONST.NEW_MODEL_FACET_NAME,
modelId: modelId
};
var newProperty = PropertyService.createModelProperty(propConfig);
newProperty.
then(function (result) {
growl.addSuccessMessage("property created");
$scope.activeInstance.properties.push(result);
$scope.activeModelPropertiesChanged = !$scope.activeModelPropertiesChanged;
}
);
}
else {
$log.warn('create model property called without valid modelId');
}
};
function getRandomNumber() {
return Math.floor((Math.random() * 100) + 1);
}
// open datasource editor
$scope.createDatasourceViewRequest = function(initialData) {
$scope.instanceType = CONST.DATASOURCE_TYPE;
var isNewOpen = IAService.isNewDataSourceOpen();
// check to see if new mode is already open
if (isNewOpen) {
// if it is check if it is active
// if new model is open but not active then just close it and
// start a fresh one
if ($scope.activeInstance && ($scope.activeInstance.id !== CONST.NEW_DATASOURCE_PRE_ID)) {
$scope.openInstanceRefs = IAService.closeInstanceById(CONST.NEW_DATASOURCE_PRE_ID);
$scope.createNewInstance(CONST.DATASOURCE_TYPE, initialData);
}
}
else {
$scope.createNewInstance(CONST.DATASOURCE_TYPE, initialData);
}
};
// save DataSource instance
$scope.saveDataSourceInstanceRequest = function(targetInstance) {
var targetDef = targetInstance.definition;
if (targetDef.id) {
var originalDataSourceId = targetDef.id;
// make sure there is a facetName
if (!targetDef.facetName) {
targetDef.facetName = CONST.NEW_DATASOURCE_FACET_NAME;
}
// create DataSourceDefinition
// double check to clear out 'new' id
if (targetDef.id === CONST.NEW_DATASOURCE_PRE_ID) {
delete targetDef.id;
return DataSourceService.createDataSourceInstance(targetInstance).
then(function(instance) {
$scope.activeInstance = instance;
IAService.updateOpenInstanceRef(originalDataSourceId, $scope.activeInstance.type, $scope.activeInstance);
$scope.updateActiveInstance(
$scope.activeInstance, CONST.DATASOURCE_TYPE, originalDataSourceId);
loadDataSources();
growl.addSuccessMessage("data source created");
$rootScope.$broadcast('IANavEvent');
return $scope.activeInstance;
}
);
}
// update DataSourceDefinition
else {
return DataSourceService.updateDataSourceInstance(targetInstance).
then(function(instance) {
growl.addSuccessMessage('data source updated');
$scope.activeInstance = instance;
IAService.updateOpenInstanceRef(originalDataSourceId, $scope.activeInstance.type, $scope.activeInstance);
$scope.updateActiveInstance(
$scope.activeInstance, CONST.DATASOURCE_TYPE, originalDataSourceId);
loadDataSources();
return $scope.activeInstance;
})
.catch(function(error){
$log.warn('bad data source definition update: ' + error);
growl.addErrorMessage('error updating data source definition');
}
);
}
}
};
// test datasource connection
$scope.testDataSourceConnection = function(instance) {
var dsDef = instance.definition;
if (!dsDef.connector) {
growl.addErrorMessage('Failed: missing connector');
return;
}
return $scope.saveDataSourceInstanceRequest(instance)
.then(function(data) {
return DataSourceService.testDataSourceConnection(data.id);
})
.catch(function(err) {
return {
status: false,
error: {
message: 'Failed.'
}
};
});
};
// global exception
$scope.clearGlobalException = function() {
$scope.globalExceptionStack = IAService.clearGlobalExceptionStack();
};
/*
*
* DISCOVERY
*
* */
// new schema event
$scope.$on('newSchemaModelsEvent', function(event, message){
$scope.openInstanceRefs = IAService.getOpenInstanceRefs();
$scope.activeInstance = IAService.getActiveInstance();
$scope.instanceType = 'model';
$scope.activeInstance.type = 'model';
// note that the handler is passed the problem domain parameters
loadModels();
$scope.setApiModelsDirty();
});
$scope.createModelsFromDS = function(id) {
DataSourceService.testDataSourceConnection(id)
.then(function(response) {
if (response.status) {
// open a modal window and trigger the discovery flow
var modalConfig = DiscoveryService.getDiscoveryModalConfig(id);
var modalInstance = IAService.openModal(modalConfig);
modalInstance.opened.then(function() {
window.setUI();
});
}
else {
$rootScope.$broadcast('GlobalExceptionEvent', response.error);
}
})
.catch(function(error) {
$rootScope.$broadcast('GlobalExceptionEvent', error);
});
};
/*
*
* Event Listeners
*
* keep these to absolute minimum - in principle
*
* */
$scope.$on('GlobalExceptionEvent', function(event, data) {
$scope.globalExceptionStack = IAService.setGlobalException(data);
});
// nav event
$scope.$on('IANavEvent', function(event, data) {
var currActiveInstance = $scope.activeInstance;
var openInstanceRefs = IAService.getOpenInstanceRefs();
// check if there is an active instance
if (currActiveInstance && currActiveInstance.id) {
var instanceObjRef = {
id:currActiveInstance.id,
name:currActiveInstance.name,
type:currActiveInstance.type
};
// check if there are open instances and that activeInstance is in it
if (openInstanceRefs.length > 0) {
var isActiveInstanceOpen = false;
// loop over the refs to confirm active instance is in there
for (var i = 0;i < openInstanceRefs.length;i++) {
if (openInstanceRefs[i].id === currActiveInstance.id) {
// active instance is open
isActiveInstanceOpen = true;
break;
}
}
// if it isn't in there we need to add it
if (!isActiveInstanceOpen) {
// add active instance to open instance refs
$scope.openInstanceRefs = IAService.addInstanceRef(instanceObjRef);
}
}
else {
// we have a mismatch - we have an active instance but no open instance refs
// add active instance to openInstanceRefs
$scope.openInstanceRefs = IAService.addInstanceRef(instanceObjRef);
}
}
else {
// there is no active instance so confirm there are no open instance refs
if (openInstanceRefs.length > 0) {
// we have a mismatch - we have open instances but none are active
// set the 0 index open instance ref to the active instance
// active the first instance by default
IAService.activateInstanceById(openInstanceRefs[0].id, openInstanceRefs[0].type).
then(function(instance) {
$scope.activeInstance = instance;
$rootScope.$broadcast('IANavEvent');
}
);
}
else {
// there are no open instances so we're good - everything is closed
}
}
$scope.openInstanceRefs = IAService.getOpenInstanceRefs();
});
}
]);