Commit cc2d8860 by Shwetha GS

ATLAS-279 UI not displaying results for certain successful select search queries…

ATLAS-279 UI not displaying results for certain successful select search queries (anilsg via shwethags)
parent 4aef164c
......@@ -28,7 +28,7 @@
```
git clone $git-repo-url
git checkout dal
git checkout master
cd dashboard/v2
npm install
grunt server
......
......@@ -353,4 +353,16 @@ Tags on Home Page design
.anchorAbsolute {
position: absolute;
right: 4px;
}
.addTag{
margin-top: -35px;
}
.tagAlign{
text-align: center;
}
.h160 {
height: 160px !important;
}
\ No newline at end of file
......@@ -17,7 +17,7 @@
*/
.add-tag {
margin-top: -7px;
margin-top: -50px;
}
.inputs input{
height: 50px;
......@@ -113,4 +113,11 @@
}
.tagsAdded{
display: inline;
}
.noTags {
font-size: 24px;
font-weight: bold;
text-align: center;
}
}
\ No newline at end of file
......@@ -44,9 +44,10 @@
<header class="navbar navbar-static-top navbar-top" data-role="navigation">
<div class="container" data-ng-include="'/modules/home/views/header.html'"></div>
</header>
<div class="content">
<div class="content container">
<div data-ng-include="'/modules/notification/views/notifications.html'"></div>
<div data-ui-view class="container"></div>
<div class="col-lg-2 padding0" data-ng-include="'/modules/navigation/views/navigation.html'" ng-class="leftNav ? 'hide' : ''" ></div>
<div data-ui-view class="ngView"></div>
</div>
<footer class="footer navbar-bottom">
<div class="container">
......
......@@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
angular.module('dgc', ['ngCookies',
......@@ -81,4 +80,31 @@ angular.module('dgc').factory('lodash', ['$window',
$rootScope.$on('$stateChangeStart', function() {
d3.selectAll('.d3-tip').remove();
});
}]);
$rootScope.updateTags = function(added, obj) {
if (added) {
$rootScope.$broadcast('add_Tag', obj);
}
};
$rootScope.loadTraits = function() {
$rootScope.$broadcast('load_Traits');
};
$rootScope.$on('$stateChangeSuccess', function(evt, to, toParams, from) {
if (from.name !== '' && to.name === 'search' && to.name !== from.name && typeof to.parent === 'undefined' && typeof from.parent === 'undefined') {
$rootScope.loadTraits();
} else if (from.name === '' && to.name === 'search') {
$rootScope.loadTraits();
}
if (typeof to.parent === 'undefined') {
if (to.name !== 'search') {
$rootScope.leftNav = true;
} else {
$rootScope.leftNav = false;
}
}
});
}]);
\ No newline at end of file
......@@ -27,11 +27,11 @@ angular.module('dgc.details').controller('DetailsController', ['$window', '$scop
id: $stateParams.id
}, function(data) {
$scope.details = data;
console.log(data);
$scope.schemas = data;
$scope.tableName = data.values.name;
$scope.isTable = (typeof data.typeName !== 'undefined' && data.typeName.toLowerCase().indexOf('table') !== -1) ? true : false;
$scope.onActivate('io');
$scope.isTags = (typeof data.traits !== 'undefined' && typeof data.traits === 'object') ? true : false;
});
$scope.isNumber = angular.isNumber;
......@@ -45,7 +45,7 @@ angular.module('dgc.details').controller('DetailsController', ['$window', '$scop
});
};
$scope.goDetails = function(id){
$scope.goDetails = function(id) {
$state.go("details", {
id: id
});
......@@ -55,4 +55,4 @@ angular.module('dgc.details').controller('DetailsController', ['$window', '$scop
$window.history.back();
};
}
]);
]);
\ No newline at end of file
......@@ -72,13 +72,14 @@
</tr>
</tbody>
</table>
<ng-include src="'/modules/tags/instance/views/tags.html'"/>
</table>
</tab>
<tab data-heading="Schema" data-ng-if="isTable"><ng-include src="'/modules/details/views/schema.html'"/></tab>
<!-- <tab data-heading="Output" data-ng-if="isTable" data-disable="!tableName" data-select="onActivate('outputs')"><ng-include data-table-type="outputs" src="'/modules/lineage/views/lineage.html'"/></tab>
<tab data-heading="Input" data-ng-if="isTable" data-disable="!tableName" data-select="onActivate('inputs')"><ng-include data-table-type="inputs" src="'/modules/lineage/views/lineage.html'"/></tab>
<tab data-heading="Lineage" data-ng-if="isTable" data-disable="!tableName" data-select="onActivate('io')"><ng-include data-table-type="io" src="'/modules/lineage_io/views/lineage_io.html'"/></tab> -->
<tab data-heading="Schema" data-ng-if="isTable">
<ng-include src="'/modules/details/views/schema.html'"/>
</tab>
<tab data-heading="Tags" data-ng-if="isTags">
<ng-include src="'/modules/tags/instance/views/tags.html'"/>
</tab>
</tabset>
</div>
</div>
......@@ -24,7 +24,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a data-ui-sref="search({ query: '' })" data-ui-sref-active="active" class="mainLogo">
<a data-ui-sref="search()" data-ui-sref-active="active" class="mainLogo">
<!-- <img src="../img/ApacheAtlasLogo.png" /> -->
Apache <b>Atlas</b>
</a>
......@@ -32,7 +32,7 @@
<nav class="collapse navbar-collapse" data-collapse="isCollapsed" data-role="navigation">
<ul class="navbar-nav nav pull-right menuBar" data-ng-if="isLoggedIn()">
<li data-ui-sref-active="active">
<a data-ui-sref="search({ query: '' })" class="menulink">Search</a>
<a data-ui-sref="search()" class="menulink">Search</a>
</li>
<li data-ng-repeat="item in menu" data-ui-sref-active="active">
<a data-ui-sref="{{item.state}}">{{item.title}}</a>
......
......@@ -19,7 +19,9 @@
angular.module('dgc.lineage').controller('Lineage_ioController', ['$element', '$scope', '$state', '$stateParams', 'lodash', 'LineageResource', 'd3', 'DetailsResource', '$q',
function($element, $scope, $state, $stateParams, _, LineageResource, d3, DetailsResource, $q) {
var guidsList = [];
var guidsList = [],
$$ = angular.element;
function inVertObj(edgs) {
var newEdgsObj = {};
......@@ -31,7 +33,7 @@ angular.module('dgc.lineage').controller('Lineage_ioController', ['$element', '$
}
});
return newEdgsObj;
}
}
function getCombinedLineageData(tableData, callRender) {
LineageResource.get({
......@@ -71,7 +73,7 @@ angular.module('dgc.lineage').controller('Lineage_ioController', ['$element', '$
if (callRender) {
render();
}
});
});
} else {
$scope.requested = false;
}
......@@ -130,11 +132,10 @@ angular.module('dgc.lineage').controller('Lineage_ioController', ['$element', '$
if (!$scope.lineageData) {
if ($scope.requested) {
if ($scope.type === 'io') {
console.log($scope.type);
getCombinedLineageData(lineageData, true);
} else {
getCombinedLineageData(lineageData, true);
}
}
}
} else {
render();
......@@ -305,7 +306,13 @@ angular.module('dgc.lineage').controller('Lineage_ioController', ['$element', '$
tooltip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) {
return '<pre class="alert alert-success">' + d.name + '</pre>';
var toolTip = $$("<pre>").attr("class", "alert alert-success")
.append($$("<p>").html('Name :<b>' + d.name + '</b>'));
if (d.tip && d.tip.trim() !== "") {
toolTip.append($$("<p>").html('Query: ' + d.tip));
}
return toolTip.prop("outerHTML");
});
// define the baseSvg, attaching a class for styling and the zoomListener
......@@ -597,7 +604,7 @@ angular.module('dgc.lineage').controller('Lineage_ioController', ['$element', '$
})
.text(function(d) {
var nameDis = (d.name.length > 15) ? d.name.substring(0, 15) + "..." : d.name;
$(this).attr('title', d.name);
$$(this).attr('title', d.name);
return nameDis;
})
.style("fill-opacity", 0);
......
......@@ -15,17 +15,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
angular.module('dgc.navigation').controller('NavigationController', ['$scope', 'NavigationResource',
function($scope, NavigationResource) {
$scope.leftnav = NavigationResource.get();
$scope.updateVar = function(event) {
$scope.$$prevSibling.query = angular.element(event.target).text();
};
$scope.$on('load_Traits', function() {
$scope.leftnav = NavigationResource.get();
});
}
]);
]);
\ No newline at end of file
......@@ -16,7 +16,7 @@
~ limitations under the License.
-->
<div data-ng-controller="NavigationController" class="mainTags">
<div data-ng-controller="NavigationController" class="mainTags leftNavigation">
<h4>Tags</h4>
<div class="list-group">
<a ng-repeat="nav in leftnav" ui-sref="search({ query: nav })" class="list-group-item limitSize" title="{{nav}}"><i class="fa fa-tag"></i> {{nav}} </a>
......
......@@ -15,24 +15,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
angular.module('dgc.tags.definition').controller('DefinitionTagsController', ['$scope', '$resource', '$state', '$stateParams', 'lodash', 'AttributeDefinition', 'TagClasses', 'TagsResource', 'NotificationService',
function($scope, $resource, $state, $stateParams, _, AttributeDefinition, Categories, TagsResource, NotificationService) {
angular.module('dgc.tags.definition').controller('DefinitionTagsController', ['$scope', '$resource', '$state', '$stateParams', 'lodash', 'AttributeDefinition', 'TagClasses', 'TagsResource', 'NotificationService', 'NavigationResource',
function($scope, $resource, $state, $stateParams, _, AttributeDefinition, Categories, TagsResource, NotificationService, NavigationResource) {
$scope.categoryList = Categories;
$scope.category = 'TRAIT';
$scope.tagModel = {
typeName: null,
superTypes: [],
attributeDefinitions: []
};
$scope.typesList = NavigationResource.get();
$scope.addAttribute = function AddAttribute() {
$scope.tagModel.attributeDefinitions.push(AttributeDefinition.getModel());
};
$scope.removeAttribute = function(index){
$scope.tagModel.attributeDefinitions.splice(index,1);
$scope.removeAttribute = function(index) {
$scope.tagModel.attributeDefinitions.splice(index, 1);
};
$scope.categoryChange = function CategorySwitched() {
......@@ -42,6 +43,7 @@ angular.module('dgc.tags.definition').controller('DefinitionTagsController', ['$
$scope.save = function saveTag(form) {
$scope.savedTag = null;
if (form.$valid) {
$scope.tagModel.superTypes = $scope.selectedParent;
$scope.categoryInst = Categories[$scope.category];
$scope.categoryInst.clearTags().addTag($scope.tagModel);
......@@ -59,4 +61,4 @@ angular.module('dgc.tags.definition').controller('DefinitionTagsController', ['$
}
};
}
]);
]);
\ No newline at end of file
......@@ -39,6 +39,14 @@
<input type="text" class="form-control" name="typeName" id="typeName" placeholder="Tag Name" data-ng-model="tagModel.typeName" required/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="ParentTag">Parent Tag</label>
<div class="col-sm-4">
<select ng-model="selectedParent" class="form-control h160" id="ParentTag" name="ParentTag" multiple>
<option ng-repeat="data in typesList" title="{{data}}">{{data}}</option>
</select>
</div>
</div>
<ng-form name="attributeForm">
<div class="form-group" data-ng-class="{'has-error': attributeForm.name.$invalid && attributeForm.name.$dirty}"
data-ng-repeat-start="attribute in tagModel.attributeDefinitions">
......@@ -55,7 +63,7 @@
</div>
</div>
<div class="form-group" data-ng-class="{'has-error': attributeForm.name.$invalid && attributeForm.name.$dirty}" data-ng-show="attribute.$$show">
<label for="attributeDatatype_{{$index}}" class="col-sm-2 control-label">Data Type Name</label>
<label for="attributeDatatype_{{$index}}" class="col-sm-2 control-label">Data Type </label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="attributeDatatype_{{$index}}" placeholder="dataTypeName"
......
......@@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
angular.module('dgc.tags.instance').controller('CreateTagController', ['$scope', 'DetailsResource', '$modalInstance', 'typesList', 'lodash', 'TagsResource', '$stateParams', '$rootScope', 'TagClasses', 'NotificationService',
......@@ -23,29 +22,44 @@ angular.module('dgc.tags.instance').controller('CreateTagController', ['$scope',
if (typesList) {
$scope.typesList = typesList;
}
var $$ = angular.element;
$scope.categoryList = Categories;
$scope.category = 'TRAIT';
$scope.getAttributeDefinations = function() {
$scope.propertiesList = {};
$scope.isRequired = {};
$scope.getAttributeApi($scope.selectedType);
};
$scope.getAttributeApi = function(tagName) {
TagsResource.get({
id: $scope.selectedType
id: tagName
}, function(data) {
var instanceType = Categories[$scope.category].instanceInfo();
if (instanceType) {
var traitTypes = angular.fromJson(data.definition)[instanceType][0];
if (traitTypes) {
$scope.propertiesList = {};
$scope.isRequired = {};
_.each(traitTypes.attributeDefinitions, function(value) {
$scope.propertiesList[value.name] = '';
$scope.isRequired[value.name] = value.isRequired;
});
var traitTypes = angular.fromJson(data.definition)[instanceType];
for (var t = 0; t < traitTypes.length; t++) {
if (traitTypes[t]) {
for(var indx = 0; indx < traitTypes[t].attributeDefinitions.length; indx++)
{
var attrDefn = traitTypes[t].attributeDefinitions[indx];
$scope.propertiesList[attrDefn.name] = '';
$scope.isRequired[attrDefn.name] = attrDefn.isRequired;
}
}
if (traitTypes[t].superTypes && traitTypes[t].superTypes.length > 0) {
for (var s = 0; s < traitTypes[t].superTypes.length; s++) {
$scope.getAttributeApi(traitTypes[t].superTypes[s]);
}
}
}
}
});
};
$scope.ok = function(tagDefinitionform) {
$scope.ok = function($event, tagDefinitionform) {
if (tagDefinitionform.$valid) {
var requestObject = {
"jsonClass": "org.apache.atlas.typesystem.json.InstanceSerialization$_Struct",
......@@ -54,8 +68,14 @@ angular.module('dgc.tags.instance').controller('CreateTagController', ['$scope',
};
DetailsResource.saveTag({
id: $stateParams.id
}, requestObject).$promise.then(function() {
$rootScope.$broadcast('refreshResourceData', $stateParams.id);
}, requestObject).$promise.then(function(data) {
if (data.requestId !== undefined && data.GUID === $stateParams.id) {
var tagName = $$("#tagDefinition").val();
$rootScope.updateTags(true, {
added: $scope.selectedType
});
$$("#" + $stateParams.id).append("<a class='tabsearchanchor ng-binding ng-scope' data-ui-sref='search({query: " + tagName + "})' title='" + tagName + "' href='#!/search?query=" + tagName + "'>" + tagName + "<span> </span></a>");
}
NotificationService.info('Tag "' + $scope.selectedType + '" has been added to entity', true);
$modalInstance.close(true);
}).catch(function(err) {
......@@ -69,4 +89,4 @@ angular.module('dgc.tags.instance').controller('CreateTagController', ['$scope',
$modalInstance.dismiss('cancel');
};
}
]);
]);
\ No newline at end of file
......@@ -15,34 +15,50 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
angular.module('dgc.tags.instance').controller('InstanceTagController', ['$scope', 'DetailsResource', '$stateParams', '$state',
function($scope, DetailsResource, $stateParams, $state) {
$scope.id = $stateParams.id;
var $$ = angular.element;
function getResourceData() {
DetailsResource.get({
id: $stateParams.id
}, function(data) {
angular.forEach(data.traits, function(obj, trait) {
var pair_arr = [];
if (obj.values !== null && Object.keys(obj.values).length > 0) {
angular.forEach(obj.values, function(value, key) {
var pair = key+":"+value;
pair_arr.push(pair);
});
data.traits[trait].values = pair_arr.join(" | ");
} else {
data.traits[trait].values = 'NA';
}
});
angular.forEach(data.traits, function(obj, trait) {
var pair_arr = [];
if (obj.values !== null && Object.keys(obj.values).length > 0) {
angular.forEach(obj.values, function(value, key) {
var pair = key + ":" + value;
pair_arr.push(pair);
});
data.traits[trait].values = pair_arr.join(" | ");
} else {
data.traits[trait].values = 'NA';
}
});
$scope.traitsList = data.traits;
if ($.isEmptyObject($scope.traitsList)) {
$scope.noTags = true;
}
});
}
$scope.$on('add_Tag', function(evt, obj) {
$scope.traitsList[obj.added] = {
typeName: obj.added
};
if ($.isEmptyObject($scope.traitsList)) {
$scope.noTags = true;
} else {
$scope.noTags = false;
}
});
$scope.openAddTag = function() {
$state.go('addTag', {
id: $scope.id
......@@ -50,25 +66,35 @@ angular.module('dgc.tags.instance').controller('InstanceTagController', ['$scope
};
$scope.detachTag = function($event, name) {
var r = confirm("Please confirm delete.");
if (r === true) {
$scope.displayName = name;
$$('#btnDelete').modal().on('click', function(e) {
e.preventDefault();
$$("#myModal").modal();
DetailsResource.detachTag({
id: $stateParams.id,
tagName: name
}, function(data) {
console.log("Detached Tag");
console.log(data);
if (data.requestId !== undefined && data.GUID === $stateParams.id && data.traitName === name) {
var curent = $event.currentTarget;
curent.parentElement.remove();
$(".popover").remove();
$$($event.currentTarget).closest('tr').remove();
delete $scope.traitsList[name];
if ($.isEmptyObject($scope.traitsList)) {
$scope.noTags = true;
} else {
$scope.noTags = false;
}
}
});
}
});
};
$scope.cancel = function() {
$$(".modal-backdrop").remove();
};
getResourceData();
$scope.$on('refreshResourceData', getResourceData);
}
]);
]);
\ No newline at end of file
......@@ -52,7 +52,7 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-success" type="submit" ng-click="ok(tagDefinitionform)" ng-disabled="tagDefinitionform.$invalid">Save</button>
<button class="btn btn-success" type="submit" ng-click="ok($event, tagDefinitionform)" ng-disabled="tagDefinitionform.$invalid">Save</button>
<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
</div>
</form>
......
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div data-ng-controller="InstanceTagController">
<div class="container tag-list wordBreak">
<h4 class="tagsAdded">Tags</h4>
<a ng-click="openAddTag()" href="" class="add-tag btn btn-primary pull-right">Add tag</a>
<ul class="tagalign">
<li ng-repeat="trait in traitsList" class="list-group-item pointer tabsearchanchor maxwidth125px" popover="{{trait.values}}" popover-title="{{trait.typeName}} Values" popover-trigger="mouseenter" >
{{trait.typeName}} <a href="" class="anchorAbsolute" ng-click="detachTag($event, trait.typeName)"> <i class="fa fa-times"></i> </a> </li>
</ul>
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div data-ng-controller="InstanceTagController" >
<div>
<a ng-click="openAddTag()" class="add-tag btn btn-primary pull-right">Add Tag</a>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Tag</th>
<th>Tools</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="trait in traitsList" class="pointer" ng-if="!noTags">
<td class="col-lg-11" >
{{trait.typeName}}
</td>
<td class="col-lg-1 tagAlign">
<a href="" class="deleteTag confirm-delete" data-toggle="modal" data-target="#myModal" ><i class="fa fa-trash-o " ng-click="detachTag($event, trait.typeName)"></i></a>
</td>
</tr>
</tbody>
</table>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Are you sure you want to delete ? </h4>
</div>
<div class="modal-body">
<b> Tag : {{displayName}} </b>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="cancel()">Close</button>
<button type="button" id="btnDelete" class="btn btn-primary" data-dismiss="modal">Delete</button>
</div>
</div>
</div>
</div>
<div ng-if="noTags" class="noTags">
No tags to display
</div>
</div>
\ No newline at end of file
......@@ -19,12 +19,12 @@ hive_partition(ClassType) - super types [Referenceable] - attributes [values, ta
hive_process(ClassType) - super types [Process] - attributes [startTime, endTime, userName, operationType, queryText, queryPlan, queryId, queryGraph]
</verbatim>
The entities are created and de-duped using unique qualified name. They provide namespace and can be used for querying/lineage as well. Note that dbName and tableName should be in lower case. clusterName is explained below:
hive_db - attribute qualifiedName - <dbName>@<clusterName>
hive_table - attribute name - <dbName>.<tableName>@<clusterName>
hive_column - attribute qualifiedName - <dbName>.<tableName>.<columnName>@<clusterName>
hive_partition - attribute qualifiedName - <dbName>.<tableName>.<partitionValues('-' separated)>@<clusterName>
hive_process - attribute name - <queryString> - trimmed query string in lower case
The entities are created and de-duped using unique qualified name. They provide namespace and can be used for querying/lineage as well. Note that dbName and tableName should be in lower case. clusterName is explained below.
* hive_db - attribute qualifiedName - <dbName>@<clusterName>
* hive_table - attribute name - <dbName>.<tableName>@<clusterName>
* hive_column - attribute qualifiedName - <dbName>.<tableName>.<columnName>@<clusterName>
* hive_partition - attribute qualifiedName - <dbName>.<tableName>.<partitionValues('-' separated)>@<clusterName>
* hive_process - attribute name - <queryString> - trimmed query string in lower case
---++ Importing Hive Metadata
......
......@@ -9,6 +9,7 @@ ATLAS-54 Rename configs in hive hook (shwethags)
ATLAS-3 Mixed Index creation fails with Date types (sumasai via shwethags)
ALL CHANGES:
ATLAS-279 UI not displaying results for certain successful "select" search queries (anilsg via shwethags)
ATLAS-242 The qualified name for hive entities should be backward compatible (shwethags)
ATLAS-361 Add validation when index backends are switched in ATLAS configuration (sumasai via shwethags)
ATLAS-171 Ability to update type definition(shwethags via sumasai)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment