+
Audio distribution for
{{:: p.name}}
@@ -48,7 +48,9 @@ Audio distribution for
diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js
index 75db5b22..5e5141b3 100644
--- a/src/baw.paths.nobuild.js
+++ b/src/baw.paths.nobuild.js
@@ -58,6 +58,7 @@ module.exports = function (environment) {
"analysisResults": {
"system": "/analysis_jobs/system/audio_recordings/{recordingId}",
"job": "/analysis_jobs/{analysisJobId}/audio_recordings/{recordingId}",
+ "jobWithPath": "/analysis_jobs/{analysisJobId}/audio_recordings{path}"
}
},
"links": {
@@ -83,11 +84,10 @@ module.exports = function (environment) {
// The following intentionally are not prefixed with a '/'
// static files
"files": {
- "error404": "error/error_404.tpl.html",
+ "error404": "error/error404.tpl.html",
"home": "home/home.tpl.html",
"login": {
- "loginWidget": "login/widget/loginWidget.tpl.html",
- "defaultImage": "assets/img/user_spanhalf.png"
+ "loginWidget": "login/widget/loginWidget.tpl.html"
},
"listen": "listen/listen.tpl.html",
"annotationViewer": "annotationViewer/annotationViewer.tpl.html",
@@ -97,7 +97,11 @@ module.exports = function (environment) {
"list": "annotationLibrary/annotationLibrary.tpl.html",
"item": "annotationLibrary/annotationItem.tpl.html"
},
- "navigation": "navigation/navigation.tpl.html",
+ "navigation": {
+ "crumbs": "navigation/navigation.tpl.html",
+ "left": "navigation/leftNavBar.tpl.html",
+ "right": "navigation/rightNavBar.tpl.html"
+ },
"birdWalk": {
"list": "birdWalks/birdWalks.tpl.html",
"detail": "birdWalks/birdWalk.tpl.html",
@@ -118,7 +122,26 @@ module.exports = function (environment) {
"distributionVisualisation": "d3Bindings/eventDistribution/distributionVisualisation.tpl.html"
}
},
- "visualize": "visualize/visualize.tpl.html"
+ "visualize": "visualize/visualize.tpl.html",
+ "jobs": {
+ details: "jobs/details/jobDetails.tpl.html",
+ list: "jobs/list/jobsList.tpl.html",
+ "new": "jobs/new/jobNew.tpl.html"
+ },
+ "analysisResults": {
+ "fileList": "analysisResults/fileList/fileList.tpl.html"
+ },
+ "users": {
+ "userTile": "users/userTile.tpl.html"
+ },
+ "savedSearches": {
+ "new": "savedSearches/widgets/newSavedSearch.tpl.html",
+ "list": "savedSearches/widgets/listSavedSearches.tpl.html",
+ "show": "savedSearches/widgets/showSavedSearch.tpl.html"
+ },
+ "scripts": {
+ "show": "scripts/widgets/showScript.tpl.html"
+ }
},
// routes used by angular
"ngRoutes": {
@@ -132,10 +155,26 @@ module.exports = function (environment) {
"d3": "/demo/d3",
"rendering": "/demo/rendering",
"bdCloud": "/demo/BDCloud2014"
+ },
+ analysisJobs: {
+ list: "/analysis_jobs",
+ "new": "/analysis_jobs/new",
+ details: "/analysis_jobs/{analysisJobId}",
+ analysisResults: "/analysis_jobs/{analysisJobId}/results:path*?"
}
},
// general links for use in 's
- "links": {}
+ "links": {
+ analysisJobs: {
+ analysisResults: "/analysis_jobs/{analysisJobId}/results",
+ analysisResultsWithPath: "/analysis_jobs/{analysisJobId}/results{path}"
+ }
+ },
+ "assets": {
+ "users": {
+ "defaultImage": "assets/img/user_spanhalf.png"
+ }
+ }
}
};
@@ -187,9 +226,9 @@ module.exports = function (environment) {
var isArray = f instanceof Array;
if (isArray) {
- wasAnyArray = true;
- f.forEach(function(item, index) {
- path.push(processFragment(item, i === (fragments.length - 1)));
+ wasAnyArray = true;
+ f.forEach(function (item, index) {
+ path.push(processFragment(item, i === (fragments.length - 1)));
});
}
else {
@@ -229,6 +268,8 @@ module.exports = function (environment) {
recursivePath(paths.api.links, paths.api.root);
recursivePath(paths.site.files, paths.site.root);
recursivePath(paths.site.ngRoutes, paths.api.root);
+ recursivePath(paths.site.assets, joinPathFragments(environment.siteRoot, environment.siteDir));
return paths;
-};
+}
+;
diff --git a/src/components/directives/angular-ui/bootstrap/bootstrap.js b/src/components/directives/angular-ui/bootstrap/bootstrap.js
index ac22c084..f300969d 100644
--- a/src/components/directives/angular-ui/bootstrap/bootstrap.js
+++ b/src/components/directives/angular-ui/bootstrap/bootstrap.js
@@ -1,7 +1,8 @@
angular.module(
"bawApp.directives.ui.bootstrap",
[
- "bawApp.directives.ui.bootstrap.pagination"
+ "bawApp.directives.ui.bootstrap.pagination",
+ "bawApp.directives.angular-ui.bootstrap.datepicker.dateRangeValidator"
]);
diff --git a/src/components/directives/angular-ui/bootstrap/datepicker/dateRangeValidator.js b/src/components/directives/angular-ui/bootstrap/datepicker/dateRangeValidator.js
new file mode 100644
index 00000000..a811efdf
--- /dev/null
+++ b/src/components/directives/angular-ui/bootstrap/datepicker/dateRangeValidator.js
@@ -0,0 +1,143 @@
+angular
+ .module("bawApp.directives.angular-ui.bootstrap.datepicker.dateRangeValidator", [])
+ .service("bawDateRangeCache", [
+ "$timeout",
+ function ($timeout) {
+ let minDateControls = new Set(),
+ maxDateControls = new Set();
+
+ let revalidating = false;
+
+
+ /**
+ * For the model to update by simulating a user entering a value.
+ * The problem with the standard $validate function is that it assigns
+ * `undefined` to the model value if validation fails. We DON'T want that
+ * ... we just want to update the validation state
+ * @param ngModel
+ */
+ function forceUpdate(ngModel) {
+ ngModel.$$lastCommittedViewValue = null;
+ ngModel.$commitViewValue(ngModel.$viewValue);
+ }
+
+ function revalidateAll() {
+ if (!revalidating) {
+ revalidating = true;
+ $timeout(() => {
+ minDateControls.forEach(forceUpdate);
+ maxDateControls.forEach(forceUpdate);
+ revalidating = false;
+ });
+ }
+ }
+
+ return {
+ addMinDateControl(control) {
+ minDateControls.add(control);
+ },
+ addMaxDateControl(control) {
+ maxDateControls.add(control);
+ },
+ revalidateMinDateControls(m, v) {
+ revalidateAll();
+ },
+ revalidateMaxDateControls(m, v) {
+ revalidateAll();
+ }
+ };
+ }
+ ])
+ .directive("bawMinDate", [
+ "$parse",
+ "bawDateRangeCache",
+ function ($parse, bawDateRangeCache) {
+ return {
+ scope: false,
+ restrict: "A",
+ require: ["ngModel", "^^form"],
+ link: function (scope, element, attributes, [ngModel, FormController]) {
+
+ let expression = $parse(attributes.bawMinDate);
+
+ bawDateRangeCache.addMinDateControl(ngModel);
+
+ ngModel.$validators.minDate = function (modelValue, viewValue) {
+ // if its not a date we don't care
+ // leave other validators to parse date validity or requiredness
+ let value = modelValue,
+ result = false;
+
+ if (!angular.isDate(value) || isNaN(value)) {
+ result = true;
+ }
+ else {
+ let minDate = expression(scope);
+
+ // we don't want the validator to check when the limit is missing
+ if (!minDate) {
+ result = true;
+ }
+ else if (value >= minDate) {
+ result = true;
+ }
+ }
+
+ // trigger a validation for the other half of the range controls
+ bawDateRangeCache.revalidateMaxDateControls(modelValue, viewValue);
+
+ return result;
+ };
+ }
+ };
+ }
+ ]
+ )
+ .directive("bawMaxDate", [
+ "$parse",
+ "bawDateRangeCache",
+ function ($parse, bawDateRangeCache) {
+ return {
+ scope: false,
+ restrict: "A",
+ require: ["ngModel", "^^form"],
+ link: function (scope, element, attributes, [ngModel, FormController]) {
+
+ let expression = $parse(attributes.bawMaxDate);
+
+ bawDateRangeCache.addMaxDateControl(ngModel);
+
+ ngModel.$validators.maxDate = function (modelValue, viewValue) {
+ // if its not a date we don't care
+ // leave other validators to parse date validity or requiredness
+ let value = modelValue,
+ result = false;
+
+ if (!angular.isDate(value) || isNaN(value)) {
+ result = true;
+ }
+ else {
+ let maxDate = expression(scope);
+
+ // we don't want the validator to check when the limit is missing
+ if (!maxDate) {
+ result = true;
+ }
+ else if (value <= maxDate) {
+ result = true;
+ }
+ }
+
+ // trigger a validation for the other half of the range controls
+ bawDateRangeCache.revalidateMinDateControls(modelValue, viewValue);
+
+ return result;
+ };
+
+
+ }
+ };
+ }
+ ]
+ );
+
diff --git a/src/components/directives/angular-ui/bootstrap/pagination/pagination.js b/src/components/directives/angular-ui/bootstrap/pagination/pagination.js
index 8a8b13ed..3a3e18d8 100644
--- a/src/components/directives/angular-ui/bootstrap/pagination/pagination.js
+++ b/src/components/directives/angular-ui/bootstrap/pagination/pagination.js
@@ -1,12 +1,48 @@
angular.module(
"bawApp.directives.ui.bootstrap.pagination",
- [])
-.run([
+ ["ui.bootstrap.pagination"])
+ .run([
"$templateCache",
function($templateCache) {
- // override bootstrap-ui's default template
- var newTemplate = $templateCache.get("components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html");
- $templateCache.put("template/pagination/pagination.html", newTemplate);
+ // add ng-href and remove ng-click
+ const
+ targetTemplate = "uib/template/pagination/pagination.html",
+ pageRegex = /(href).*(?:ng-click="selectPage\(([^,]+), \$event\)")/gm,
+ replaceString = `ng-href="{{ pagination.href($2) }}" href`;
+
+ var oldTemplate = $templateCache.get(targetTemplate);
+
+ var newTemplate = oldTemplate.replace(pageRegex, replaceString);
+
+ $templateCache.put(targetTemplate, newTemplate);
+ }])
+ .directive("paginationHref", ["$parse", function($parse) {
+ return {
+ require: ["uibPagination"],
+ controller: "UibPaginationController",
+ controllerAs: "pagination",
+ replace: true,
+ link: function(scope, element, attrs, ctrls) {
+ var paginationCtrl = ctrls[0];
+ let parentScope = scope;
+
+ // this is dodgy AF but its the only way i can think of to get
+ // the instance for which the actual expression is attached!
+ let parts = attrs.paginationHref.split(".");
+ let parent = parentScope;
+ if (parts.length > 1) {
+ parts.splice(-1, 1);
+ let parentExpression = parts.join(".");
+ parent = $parse(parentExpression)(parentScope);
+ }
+
+ let f = $parse(attrs.paginationHref)(parentScope);
+ paginationCtrl.href = function (...args) {
+ return f.apply(parent, args);
+ };
+ }
+ };
}]);
+
diff --git a/src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html b/src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html
deleted file mode 100644
index 61288f5b..00000000
--- a/src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
\ No newline at end of file
diff --git a/src/components/directives/bawAnnotationViewer.js b/src/components/directives/bawAnnotationViewer.js
index f724616b..44458bc7 100644
--- a/src/components/directives/bawAnnotationViewer.js
+++ b/src/components/directives/bawAnnotationViewer.js
@@ -1,4 +1,7 @@
-var bawds = bawds || angular.module("bawApp.directives", ["bawApp.configuration", "bawApp.directives.ui.bootstrap"]);
+var bawds = bawds || angular.module("bawApp.directives", [
+ "bawApp.configuration",
+ "bawApp.directives.ui.bootstrap",
+ "bawApp.directives.formChildrenHack"]);
bawds.directive("bawAnnotationViewer",
[ "conf.paths",
diff --git a/src/components/directives/directives.js b/src/components/directives/directives.js
index 9f1454eb..d4fb6729 100644
--- a/src/components/directives/directives.js
+++ b/src/components/directives/directives.js
@@ -1,6 +1,7 @@
var bawds = bawds || angular.module("bawApp.directives", [
"bawApp.configuration",
- "bawApp.directives.ui.bootstrap"
+ "bawApp.directives.ui.bootstrap",
+ "bawApp.directives.formChildrenHack"
]);
diff --git a/src/components/directives/drag.js b/src/components/directives/drag.js
index 13fe1516..0b952de7 100644
--- a/src/components/directives/drag.js
+++ b/src/components/directives/drag.js
@@ -36,8 +36,6 @@ ngDragabilly.directive("draggie",
dragEnd: angular.noop
};
- // TODO: make getStyleProperty a module
- var transformProperty = getStyleProperty("transform"); // jshint ignore:line
return {
restrict: "A",
@@ -46,12 +44,13 @@ ngDragabilly.directive("draggie",
},
link: function (scope, $element, attributes/*, controller, transcludeFunction*/) {
var element = $element[0];
+ const transformProperty = typeof element.style.transform === "string" ? "transform" : "WebkitTransform";
scope.options = angular.extend(defaultOptions, scope.options);
var draggie = new Draggabilly(element, scope.options);
- draggie.on("dragStart", function (draggie, event, pointer) {
+ draggie.on("dragStart", function (event, pointer) {
scope.options.dragStart(scope, draggie, event, pointer);
if (scope.options.raiseAngularEvents) {
@@ -59,7 +58,7 @@ ngDragabilly.directive("draggie",
}
});
- draggie.on("dragMove", function (draggie, event, pointer) {
+ draggie.on("dragMove", function (event, pointer) {
scope.options.dragMove(scope, draggie, event, pointer);
if (scope.options.raiseAngularEvents) {
@@ -73,7 +72,7 @@ ngDragabilly.directive("draggie",
element.style[transformProperty] = "translate3d( " + position.x + "px, " + position.y + "px, 0)";
};
- draggie.on("dragEnd", function (draggie, event, pointer) {
+ draggie.on("dragEnd", function (event, pointer) {
if (!scope.options.useLeftTop) {
reposition(draggie.element, draggie.position);
}
diff --git a/src/components/directives/formChildrenHack.js b/src/components/directives/formChildrenHack.js
new file mode 100644
index 00000000..a54e7cf4
--- /dev/null
+++ b/src/components/directives/formChildrenHack.js
@@ -0,0 +1,41 @@
+// attempts to hack a solution together for:
+// https://github.com/angular/angular.js/issues/10071
+// and
+// https://github.com/angular/angular.js/pull/11023
+// code in this module based on http://stackoverflow.com/questions/25818757/set-angularjs-nested-forms-to-submitted
+angular.module(
+ "bawApp.directives.formChildrenHack",
+ [])
+ .directive("form", function () {
+ return {
+ restrict: "E",
+ require: "form",
+ link: function (scope, elem, attrs, formCtrl) {
+ //console.debug("formChildrenHack::form::link: Link function run");
+
+ scope.$watch(function () {
+ return formCtrl.$submitted;
+ }, function (submitted) {
+ //console.debug("formChildrenHack::form::submittedWatch: submit triggered");
+ if (submitted) {
+ scope.$broadcast("$submitted");
+ }
+ });
+ }
+ };
+ })
+
+ .directive("ngForm", function () {
+ return {
+ restrict: "EA",
+ require: "form",
+ link: function (scope, elem, attrs, formCtrl) {
+ //console.debug("formChildrenHack::ngForm::link: Link function run");
+
+ scope.$on("$submitted", function () {
+ console.debug("formChildrenHack::ngForm::submittedListener: setting submitted", scope);
+ formCtrl.$setSubmitted();
+ });
+ }
+ };
+ });
\ No newline at end of file
diff --git a/src/components/directives/input[type=range].js b/src/components/directives/input[type=range].js
index 49dfb29c..480c95a1 100644
--- a/src/components/directives/input[type=range].js
+++ b/src/components/directives/input[type=range].js
@@ -1,4 +1,7 @@
-var bawds = bawds || angular.module("bawApp.directives", ["bawApp.configuration", "bawApp.directives.ui.bootstrap"]);
+var bawds = bawds || angular.module("bawApp.directives", [
+ "bawApp.configuration",
+ "bawApp.directives.ui.bootstrap",
+ "bawApp.directives.formChildrenHack"]);
bawds.directive("ngSlider", function () {
return {
diff --git a/src/components/directives/ngAudio.js b/src/components/directives/ngAudio.js
index 375872c8..205b314b 100644
--- a/src/components/directives/ngAudio.js
+++ b/src/components/directives/ngAudio.js
@@ -1,4 +1,7 @@
-var ngAudio = ngAudio || angular.module("bawApp.directives.ngAudio", ["bawApp.configuration", "bawApp.directives.ui.bootstrap"]);
+var ngAudio = ngAudio || angular.module("bawApp.directives.ngAudio", [
+ "bawApp.configuration",
+ "bawApp.directives.ui.bootstrap",
+ "bawApp.directives.formChildrenHack"]);
ngAudio.constant("ngAudioEvents", {
diff --git a/src/components/directives/ngEval.js b/src/components/directives/ngEval.js
index ab506d22..2b8d9ba3 100644
--- a/src/components/directives/ngEval.js
+++ b/src/components/directives/ngEval.js
@@ -1,4 +1,7 @@
-var bawds = bawds || angular.module("bawApp.directives", ["bawApp.configuration", "bawApp.directives.ui.bootstrap"]);
+var bawds = bawds || angular.module("bawApp.directives", [
+ "bawApp.configuration",
+ "bawApp.directives.ui.bootstrap",
+ "bawApp.directives.formChildrenHack"]);
bawds.directive("ngEval", function () {
return {
diff --git a/src/components/directives/ngGoogleMaps.js b/src/components/directives/ngGoogleMaps.js
index e35e2c42..c7dca016 100644
--- a/src/components/directives/ngGoogleMaps.js
+++ b/src/components/directives/ngGoogleMaps.js
@@ -4,7 +4,11 @@
/* globals google*/
-var bawds = bawds || angular.module("bawApp.directives", ["bawApp.configuration", "bawApp.directives.ui.bootstrap"]);
+var bawds = bawds || angular.module("bawApp.directives", [
+ "bawApp.configuration",
+ "bawApp.directives.ui.bootstrap",
+ "bawApp.directives.formChildrenHack"
+ ]);
/* Start map directives */
/** stolen from angular ui
diff --git a/src/components/filters/filters.js b/src/components/filters/filters.js
index 91d5ee24..38624a26 100644
--- a/src/components/filters/filters.js
+++ b/src/components/filters/filters.js
@@ -1,105 +1,109 @@
-
/* http://docs.angularjs.org/#!angular.filter */
- var bawfs = bawfs || angular.module("bawApp.filters", []);
-
- /*
- http://stackoverflow.com/questions/11873570/angularjs-for-loop-with-numbers-ranges
-
-
- do something
-
- */
- bawfs.filter("range", function() {
- return function(input, total) {
- total = baw.parseInt(total);
- for (var i=0; i
+ do something
+
+ */
+bawfs.filter("range", function () {
+ return function (input, total) {
+ total = baw.parseInt(total);
+ for (var i = 0; i < total; i++) {
+ input.push(i);
+ }
+ return input;
+ };
+});
+
+bawfs.filter("boolToWords", function () {
+ return function (text, truePhrase, falsePhrase) {
+ var value = JSON.parse(text);
+ if (value) {
+ return truePhrase || "";
+ }
+ else {
+ return falsePhrase || "";
+ }
+ };
+});
+
+/**
+ * moment js adapters
+ *
+ * requires momentjs
+ */
+bawfs.filter("moment", ["moment", function (moment) {
+ return function (input, method) {
+
+ if (input) {
+ var restOfArguments = Array.prototype.slice.call(arguments, 2, arguments.length);
+
+ var m = moment(input);
+ return m[method].apply(m, restOfArguments);
+
+ }
+
+ return "";
+ };
+}]);
+
+
+/**
+ * Format a given value to the with the site's default timespan formatter
+ * assumes input is in seconds
+ */
+bawfs.filter("formatTimeSpan", function () {
+ return function (input) {
+
+ if (input) {
+ return baw.secondsToDurationFormat(input);
+ }
+ else {
+ return "";
+ }
+
+ };
+});
+
+
+/**
+ * Output a tag name when given an ID
+ */
+bawfs.filter("tagName", ["Tag", function (Tag) {
+ return function (input) {
+
+ var id = parseInt(input, 10);
+
+ if (id && !isNaN(id)) {
+ var tag = Tag.resolve(id);
+
+ if (tag) {
+ return tag.text;
}
- else {
- return "";
- }
-
- };
- });
-
-
- /**
- * Output a tag name when given an ID
- */
- bawfs.filter("tagName", ["Tag", function(Tag) {
- return function(input) {
-
- var id = parseInt(input, 10);
-
- if (id && !isNaN(id)) {
- var tag = Tag.resolve(id);
-
- if (tag) {
- return tag.text;
- }
-
- return "";
- }
- else {
- return "";
- }
- };
- }]);
-
- bawfs.filter("format", function() {
- return function stringFormatFilter(string, args) {
- if (angular.isString(string)) {
- return String.format.apply(string, (arguments));
- }
-
- throw "A string is required for the first argument";
- };
- });
+ return "";
+ }
+ else {
+ return "";
+ }
+ };
+}]);
+
+bawfs.filter("format", function () {
+ return function stringFormatFilter(string, args) {
+ if (angular.isString(string)) {
+ return String.format.apply(string, (arguments));
+ }
+
+ throw "A string is required for the first argument";
+ };
+});
+
+bawfs.filter("percentage", ["$filter", function ($filter) {
+ return function (input, decimals = 2) {
+ return $filter("number")(input * 100, decimals) + "%";
+ };
+}]);
\ No newline at end of file
diff --git a/src/components/models/analysisJob.js b/src/components/models/analysisJob.js
new file mode 100644
index 00000000..c9796787
--- /dev/null
+++ b/src/components/models/analysisJob.js
@@ -0,0 +1,152 @@
+angular
+ .module("bawApp.models.analysisJob", [])
+ .constant("baw.models.AnalysisJob.progressKeys", {
+ "queued": "queued",
+ "working": "working",
+ "successful": "successful",
+ "failed": "failed",
+ "total": "total"
+ })
+ .constant("baw.models.AnalysisJob.statusKeys", {
+ "new": "new",
+ "preparing": "preparing",
+ "processing": "processing",
+ "suspended": "suspended",
+ "completed": "completed"
+ })
+ .factory("baw.models.AnalysisJob", [
+ "baw.models.associations",
+ "baw.models.ApiBase",
+ "baw.models.AnalysisJob.progressKeys",
+ "baw.models.AnalysisJob.statusKeys",
+ "UserProfile",
+ "conf.paths",
+ "$url",
+ "humanize-duration",
+ "filesize",
+ "moment",
+ function (associations, ApiBase, keys, statusKeys, UserProfile, paths, $url, humanizeDuration, filesize, moment) {
+
+ class AnalysisJob extends ApiBase {
+ constructor(resource) {
+ super(resource);
+
+ this.customSettings = this.customSettings || null;
+ this.overallStatusModifiedAt = new Date(this.overallStatusModifiedAt);
+ this.overallProgressModifiedAt = new Date(this.overallProgressModifiedAt);
+ this.overallCount = Number(this.overallCount);
+ this.overallDurationSeconds = Number(this.overallDurationSeconds);
+ this.overallSizeBytes = this.overallSizeBytes || null;
+ this.overallStatus = this.overallStatus || null;
+ this.overallProgress = this.overallProgress || null;
+ this.savedSearchId = Number(this.savedSearchId);
+ this.scriptId = this.scriptId || null;
+ this.startedAt = new Date(this.startedAt);
+ }
+
+ get isNew() {
+ return this.overallStatus === statusKeys.new;
+ }
+
+ get isPreparing() {
+ return this.overallStatus === statusKeys.preparing;
+ }
+
+ get isProcessing() {
+ return this.overallStatus === statusKeys.processing;
+ }
+
+ get isSuspended() {
+ return this.overallStatus === statusKeys.suspended;
+ }
+
+ get isCompleted() {
+ return this.overallStatus === statusKeys.completed;
+ }
+
+ get isActive() {
+ return this.isNew || this.isPreparing || this.isProcessing;
+ }
+
+ get completedRatio() {
+ return ((this.overallProgress.successful || 0) + (this.overallProgress.failed || 0)) / this.overallCount;
+ }
+
+ get successfulRatio() {
+ return (this.overallProgress.successful || 0) / this.overallCount;
+ }
+
+
+ get friendlyDuration() {
+ return humanizeDuration(this.overallDurationSeconds * 1000, {largest: 2});
+ }
+
+ get friendlyRunningTime() {
+ let lastUpdate = Math.max(+this.overallProgressModifiedAt, +this.overallStatusModifiedAt),
+ delta = +lastUpdate - +this.createdAt;
+
+ return moment.duration(delta).humanize();
+ }
+
+
+ get friendlySize() {
+ if (this.overallSizeBytes) {
+ return filesize(this.overallSizeBytes, {round: 0});
+ }
+ else {
+ return "unknown";
+ }
+ }
+
+ get friendlyUpdated() {
+ var lastUpdate = Math.max(this.overallProgressModifiedAt, this.overallStatusModifiedAt);
+
+ return moment(lastUpdate).fromNow();
+ }
+
+ get resultsUrl() {
+ return $url.formatUri(
+ paths.site.links.analysisJobs.analysisResults,
+ {analysisJobId: this.id}
+ );
+ }
+
+ get viewUrl() {
+ return $url.formatUri(
+ paths.site.ngRoutes.analysisJobs.details,
+ {analysisJobId: this.id}
+ );
+ }
+
+ static get viewListUrl() {
+ return $url.formatUri(paths.site.ngRoutes.analysisJobs.list);
+ }
+
+
+ generateSuggestedName() {
+ //let currentUserName = !!UserProfile.profile ? UserProfile.profile.userName : "(unknown user)";
+ let scriptName = !!this.script ? this.script.name : "(not chosen)";
+ let savedSearchName = !!this.savedSearch && !!this.savedSearch.name ? this.savedSearch.name : "(not chosen)";
+ return `"${scriptName}" analysis run on the "${savedSearchName}" data`;
+ }
+
+ get savedSearch() {
+ return this._savedSearch || null;
+ }
+
+ set savedSearch(value) {
+ this._savedSearch = value;
+ }
+
+ get script() {
+ return this._script || null;
+ }
+
+ set script(value) {
+ this._script = value;
+ }
+
+ }
+
+ return AnalysisJob;
+ }]);
diff --git a/src/components/models/analysisResult.js b/src/components/models/analysisResult.js
new file mode 100644
index 00000000..227f18d8
--- /dev/null
+++ b/src/components/models/analysisResult.js
@@ -0,0 +1,184 @@
+angular
+ .module("bawApp.models.analysisResult", [])
+ .factory("baw.models.AnalysisResult", [
+ "baw.models.associations",
+ "baw.models.ApiBase",
+
+ "conf.paths",
+ "$url",
+ "humanize-duration",
+ "filesize",
+ "moment",
+ "MimeType",
+ function (associations, ApiBase, paths, $url, humanizeDuration, filesize, moment, MimeType) {
+
+ class AnalysisResult extends ApiBase {
+ constructor(resource, parent) {
+ super(resource);
+
+ this._parent = parent;
+ this.path = this.path || null;
+ this.name = this.name || null;
+ this.type = this.type || null;
+ this.mime = this.mime || null;
+ this.sizeBytes = this.sizeBytes || null;
+ this.hasChildren = this.hasChildren === true || false;
+ this.hasZip = this.hasZip === true || false;
+
+ let children = [];
+ if (this.children) {
+ // recursive!
+ children = this
+ .children
+ .map(x => new AnalysisResult(x, this))
+ .sort(AnalysisResult.sort);
+ }
+ this.children = children;
+ }
+
+ get analysisJob() {
+ // allow linking back to results's parent directory to get analysisJob
+ // that generated these results
+ return this._analysisJob || (this._parent && this._parent.analysisJob);
+ }
+
+ set analysisJob(value) {
+ this._analysisJob = value;
+ }
+
+ get analysisJobId() {
+ // allow linking back to results's parent directory to get analysisJob
+ // that generated these results
+ return this._analysisJobId || (this._parent && this._parent.analysisJobId);
+ }
+
+ set analysisJobId(value) {
+ this._analysisJobId = value;
+ }
+
+ get audioRecordingId() {
+ // allow linking back to results's parent directory to get audioRecordingId
+ // that generated these results
+ return this._audioRecordingId || (this._parent && this._parent.audioRecordingId);
+ }
+
+ set audioRecordingId(value) {
+ this._audioRecordingId = value;
+ }
+
+
+ get isDirectory() {
+ return this.type === "directory";
+ }
+
+ get isFile() {
+ return this.type === "file";
+ }
+
+ get friendlySize() {
+ if (this.sizeBytes) {
+ return filesize(this.sizeBytes, {round: 0});
+ }
+ else {
+ return "";
+ }
+ }
+
+ get icon() {
+ if (this.isDirectory) {
+ return "fa fa-folder-o";
+ }
+
+ return MimeType.mimeToFaIcon(this.mime);
+ }
+
+ get path() {
+ // allow linking back to results's parent directory to get path
+ // that generated these results
+
+ if (this._path) {
+ return this._path;
+ }
+
+ if (!this._parent) {
+ return undefined;
+ }
+
+ let path = this._parent.path;
+
+ if (!path) {
+ return undefined;
+ }
+
+ if (!path.endsWith("/")) {
+ path = path + "/";
+ }
+
+ return path + this.name;
+ }
+
+ set path(value) {
+ this._path = value;
+ }
+
+
+ // url to the resource
+ get url() {
+ let analysisJobId = !this.analysisJob ? this.analysisJobId : this.analysisJob.id;
+
+ let url = paths.api.routes.analysisResults.jobWithPath;
+
+ let result = $url.formatUri(
+ url,
+ {analysisJobId, path: this.path}
+ );
+
+ return result;
+ }
+
+ get zipUrl() {
+ return this.url + ".zip";
+ }
+
+ get viewUrl() {
+ let analysisJobId = !this.analysisJob ? this.analysisJobId : this.analysisJob.id;
+
+ let url = paths.site.links.analysisJobs.analysisResultsWithPath;
+
+ let result = $url.formatUri(
+ url,
+ {analysisJobId, path: this.path}
+ );
+
+ return result;
+ }
+
+ static sort(a, b) {
+ if (!a) {
+ return -1;
+ }
+
+ if (!b) {
+ return 1;
+ }
+
+ let aDir = a.isDirectory,
+ bDir = b.isDirectory;
+
+ if (aDir && !bDir) {
+ return -1;
+ }
+
+ if (!aDir && bDir) {
+ return 1;
+ }
+
+ // if both not dirs, or if both dirs, sort on name
+ return a.name.localeCompare(b.name);
+ }
+
+
+ }
+
+ return AnalysisResult;
+ }]);
diff --git a/src/components/models/annotation.spec.js b/src/components/models/annotation.spec.js
index 4d592210..d299d60f 100644
--- a/src/components/models/annotation.spec.js
+++ b/src/components/models/annotation.spec.js
@@ -111,7 +111,7 @@ describe("The Annotation object", function () {
});
it("'s prototype should have all of the resource properties defined", function () {
- expect(baw.Annotation.prototype).toImplement({isNew: null, mergeResource: null, exportObj: null});
+ expect(baw.Annotation.prototype).toImplement({isNew: Function, mergeResource: Function, exportObj: Function});
});
diff --git a/src/components/models/associations.js b/src/components/models/associations.js
index 644adea2..8ea5897c 100644
--- a/src/components/models/associations.js
+++ b/src/components/models/associations.js
@@ -15,6 +15,33 @@ angular
constructor(resource) {
Object.assign(this, resource);
+
+ // createdAt and UpdatedAt are fairly common attributes
+ if (this.createdAt) {
+ this.createdAt = new Date(this.createdAt);
+ }
+
+ if (this.updatedAt) {
+ this.updatedAt = new Date(this.updatedAt);
+ }
+
+ if (this.creatorId) {
+ this.creatorId = Number(this.creatorId);
+ }
+
+ if (this.updaterId) {
+ this.updaterId = Number(this.updaterId);
+ }
+ }
+
+ /**
+ * If called, auto downloads linked resources.
+ * Since we wabt customiseable behaviour, we force an explicit call
+ * to autoDownload.
+ * @param resources
+ */
+ autoDownload(resources) {
+
}
static make(resource) {
@@ -54,6 +81,12 @@ angular
return response;
}
+
+ static makeFromApiWithType(Type) {
+ return function (resource) {
+ return ApiBase.makeFromApi.call(Type, resource);
+ };
+ }
}
return ApiBase;
@@ -97,17 +130,24 @@ angular
parentManyRelationSuffix = "Id" + pluralitySuffix,
id = "id",
arityMany = Symbol("many"),
- //arityOne = Symbol("one"),
- unavailable = "This parent resource is unavailable.";
+ arityOne = Symbol("one"),
+ unavailable = "This parent resource is unavailable.",
+ undefinedToUnavailable = x => x === undefined ? new ModelUnavailable(unavailable) : x,
+ linkerCache = new Map();
+
+ var getName = (n) => n instanceof Object ? n.name : n;
+ var getArity = (n) => n instanceof Object ? n.arity : arityOne;
function many(name) {
- return {
- name,
- arity: arityMany
- };
+ return {name, arity: arityMany};
}
- var associations = new Map([
+ function one(name) {
+ return {name, arity: arityOne};
+ }
+
+
+ var associations = Object.freeze(new Map([
[
"Tag", {
parents: null, children: [many("Tagging")]
@@ -145,8 +185,20 @@ angular
[
"User", {
parents: null, children: [many("Bookmark")]
+ }],
+ [
+ "SavedSearch", {
+ parents: [many("AnalysisJob")], children: null
+ }],
+ [
+ "Script", {
+ parents: [many("AnalysisJob")], children: null
+ }],
+ [
+ "AnalysisJob", {
+ parents: null, children: [one("Script"), one("SavedSearch")]
}]
- ]);
+ ]));
function chainToString(chain) {
@@ -156,13 +208,65 @@ angular
return {
generateLinker,
arrayToMap,
- makeFromApi (Type) {
- return function (resource) {
- return ApiBase.makeFromApi.call(Type, resource);
- };
- }
+ associations,
+ autoDownload
};
+
+ function autoDownload(targetType, arity = arityOne, limit = []) {
+ throw new Error("Not Implemented");
+ /*
+ // get associated models
+ let targetAssociation = associations.get(targetType),
+ models = [];
+ if (targetAssociation.parents) {
+ models = models.concat(targetAssociation.parents);
+ }
+ if (targetAssociation.children) {
+ models = models.concat(targetAssociation.children);
+ }
+
+ models = models
+ .filter(p => arity === arityMany || getArity(p) === arityOne)
+ .filter(p => limit.length === 0 || limit.indexOf(getName(p)) >= 0);
+
+ // get linkers
+ let linkers = models.map( model => generateLinker(targetType, model) );
+
+ // TODO: this won't work until services are expressed as Service functions
+ let getService = () => { };
+
+ for (let i = 0; i < models.length; i++) {
+
+
+ let model = models[i],
+ arity = getArity(model),
+ service = getService(getName(model));
+
+ if (arity === arityMany) {
+ service.filter()
+ }
+ else {
+ service.get()
+ }
+ }
+ */
+ }
+
+
+
+ function isManyAssociation(previousAssociation, currentAssociation) {
+ let p = previousAssociation.parents || [],
+ c = previousAssociation.children || [];
+
+ let a = p.concat(c).find(x => getName(x) === currentAssociation);
+ if (!a) {
+ return false;
+ }
+
+ return getArity(a) === arityMany;
+ }
+
/**
* This function determines if there is a way to link
* a child to a parent node. If there is, it returns a function
@@ -170,6 +274,11 @@ angular
*/
function generateLinker(child, parent) {
+ let cacheKey = child + "---->" + parent;
+ if (linkerCache.has(cacheKey)) {
+ return linkerCache.get(cacheKey);
+ }
+
if (!associations.has(child)) {
throw new Error("Child must be one of the known associations");
}
@@ -197,7 +306,7 @@ angular
console.debug("associations:generateLinker:", chainToString(chain));
// now make an optimised function to execute it
- return function (target, associationCollections) {
+ let linker = function (target, associationCollections) {
var currentTargets = [target];
for (let c = 1; c < chain.length; c++) {
let association = chain[c],
@@ -207,6 +316,9 @@ angular
targetName = correctCase[c] + (manyTargets ? pluralitySuffix : "");
// get the collection appropriate for the first association
+ if (!associationCollections.hasOwnProperty(association)) {
+ throw new Error(`No associations Map supplied for model ${association}`);
+ }
let possibleParentObjects = associationCollections[association];
// when following many arity associations, there may be more than one
@@ -250,8 +362,7 @@ angular
// handle the cases of missing associations
// this can sometimes happen when certain associations are
// filtered out from a dataset for security reasons
- realAssociations = realAssociations.map(
- x => x === undefined ? new ModelUnavailable(unavailable) : x);
+ realAssociations = realAssociations.map(undefinedToUnavailable);
// assign to child
currentTarget[targetName] = manyTargets ? realAssociations : realAssociations[0];
@@ -266,17 +377,8 @@ angular
return target;
};
- function isManyAssociation(previousAssociation, currentAssociation) {
- let p = previousAssociation.parents || [],
- c = previousAssociation.children || [];
-
- let a = p.concat(c).find(x => x.name === currentAssociation);
- if (!a) {
- return false;
- }
-
- return a.arity === arityMany;
- }
+ linkerCache.set(cacheKey, linker);
+ return linker;
}
/**
@@ -306,7 +408,7 @@ angular
for (var i = 0; i < nodesToVisit.length; i++) {
var n = nodesToVisit[i];
- let thisNode = n instanceof Object ? n.name : n;
+ let thisNode = getName(n);
// prevent cyclic loops
// if the new node has already been visited,
@@ -342,7 +444,7 @@ angular
*/
function arrayToMap(items) {
return new Map(
- [for (item of items) [item[id], item]]
+ items.map(item => [item[id], item])
);
}
diff --git a/src/components/models/models.js b/src/components/models/models.js
index 24216f2f..9dc3d47f 100644
--- a/src/components/models/models.js
+++ b/src/components/models/models.js
@@ -5,8 +5,10 @@ angular.module(
"rails",
"bawApp.services",
"bawApp.models.associations",
-//
+ //
// endpoint specific
+ "bawApp.models.analysisJob",
+ "bawApp.models.analysisResult",
//"bawApp.models.bookmark",
"bawApp.models.project",
"bawApp.models.site",
@@ -16,6 +18,8 @@ angular.module(
//"bawApp.models.taggings",
"bawApp.models.tag",
"bawApp.models.media",
+ "bawApp.models.savedSearch",
+ "bawApp.models.script",
//"bawApp.models.birdWalkService",
//"bawApp.models.breadcrumbs",
"bawApp.models.userProfile",
diff --git a/src/components/models/savedSearch.js b/src/components/models/savedSearch.js
new file mode 100644
index 00000000..2ce9b3c8
--- /dev/null
+++ b/src/components/models/savedSearch.js
@@ -0,0 +1,142 @@
+angular
+ .module("bawApp.models.savedSearch", [])
+ .factory("baw.models.SavedSearch", [
+ "baw.models.associations",
+ "baw.models.ApiBase",
+ "conf.paths",
+ "$url",
+ "humanize-duration",
+ "moment",
+ "QueryBuilder",
+ function (associations, ApiBase, paths, $url, humanizeDuration, moment, QueryBuilder) {
+
+ /**
+ * Represents a saved filter and its settings.
+ * The storedQuery is a QueryBuilder filter as an object.
+ * The storedQuery is executed against AudioRecordings
+ */
+ class SavedSearch extends ApiBase {
+ constructor(resource) {
+ //let model = this;
+
+ super(resource);
+
+ this._storedQuery = {};
+
+ this.id = this.id || null;
+ this.name = this.name || null;
+ this.description = this.description || null;
+ this.storedQuery = this.storedQuery || {};
+ this.projectIds = this.projectIds || null;
+ this.analysisJobIds = this.analysisJobIds || null;
+
+ // client only fields
+
+
+ if (resource) {
+ this.basicFilter = undefined;
+ }
+ else {
+ // only when new'ed on client side
+
+ // ensure properties added here are taken care of
+ // in `updateQueryFromBasicFilter` as well.
+ let basicFilterBase = {
+ projectId: null,
+ siteIds: [],
+ minimumDate: null,
+ maximumDate: null
+ };
+
+ this.basicFilter = basicFilterBase;
+ }
+ }
+
+ get friendlyUpdated() {
+ var lastUpdate = this.createdAt;
+
+ return moment(lastUpdate).fromNow();
+ }
+
+
+
+ generateSuggestedName(projects, sites) {
+ if (!this.basicFilter) {
+ return undefined;
+ }
+
+ let project = projects.find(p => p.id === this.basicFilter.projectId);
+
+ let projectName = !!project ? project.name : "(no project)";
+
+ let chosenSites = sites.filter(s => this.basicFilter.siteIds.indexOf(s.id) >=0).map(s => s.name);
+
+ let siteName = "(no sites)";
+ if (sites && this.basicFilter.siteIds.length > 0) {
+ siteName = this.basicFilter.siteIds.length === sites.length ? "All sites" : "Sites " + chosenSites.join(", ");
+ }
+
+ let dates = "",
+ min = moment(this.basicFilter.minimumDate).format("YYYY-MMM-DD"),
+ max = moment(this.basicFilter.maximumDate).format("YYYY-MMM-DD");
+ if (this.basicFilter.minimumDate && this.basicFilter.maximumDate) {
+ dates = ` between ${min} and ${max}`;
+ }
+ else if (this.basicFilter.minimumDate) {
+ dates = ` ending after ${min}`;
+ }
+ else if (this.basicFilter.maximumDate) {
+ dates = ` starting before ${max}`;
+ }
+
+ return `${siteName} in ${projectName}${dates}`;
+ }
+
+ /**
+ * Convert basic filter object graph into a
+ * QueryBuilder query. Presently needs to be called
+ * manually since es6 proxies don't exist :-(
+ */
+ updateQueryFromBasicFilter() {
+ let filter = this.basicFilter;
+
+ // query executed against audio recordings
+ var query = QueryBuilder.create(function(baseQuery) {
+ let q = baseQuery;
+
+ if (filter.projectId) {
+ q = q.eq("projects.id", filter.projectId);
+ }
+
+ if (filter.siteIds.length > 0) {
+ q = q.in("siteId", filter.siteIds);
+ }
+
+ if (filter.minimumDate) {
+ q = q.gt("recordedDate", filter.minimumDate);
+ }
+
+ if (filter.maximumDate) {
+ // NB: recordedEndDate does not currently exist.
+ q = q.lt("recordedEndDate", filter.maximumDate);
+ }
+
+ return q;
+ });
+
+ this._storedQuery = query.toJSON();
+ }
+
+ get storedQuery() {
+ return this._storedQuery;
+ }
+
+ set storedQuery(value) {
+ // TODO: querybuilder validate
+ this._storedQuery = value;
+ }
+
+ }
+
+ return SavedSearch;
+ }]);
diff --git a/src/components/models/scripts.js b/src/components/models/scripts.js
new file mode 100644
index 00000000..b64affd2
--- /dev/null
+++ b/src/components/models/scripts.js
@@ -0,0 +1,37 @@
+angular
+ .module("bawApp.models.script", [])
+ .factory("baw.models.Script", [
+ "baw.models.associations",
+ "baw.models.ApiBase",
+ "conf.paths",
+ "$url",
+ "humanize-duration",
+ "moment",
+ function (associations, ApiBase, paths, $url, humanizeDuration, moment) {
+
+ class Script extends ApiBase {
+ constructor(resource) {
+ super(resource);
+
+
+ this.version = Number(this.version);
+
+ this.executableSettings = this.executableSettings || null;
+ this.executableSettingsMediaType = this.executableSettingsMediaType || null;
+
+
+ }
+
+
+ get friendlyUpdated() {
+ var lastUpdate = this.createdAt;
+
+ return moment(lastUpdate).fromNow();
+ }
+
+
+
+ }
+
+ return Script;
+ }]);
diff --git a/src/components/models/tag.js b/src/components/models/tag.js
index e709a4b2..fc577cb4 100644
--- a/src/components/models/tag.js
+++ b/src/components/models/tag.js
@@ -3,11 +3,11 @@ angular
.factory(
"baw.models.Tag",
[
- "baw.models.associations",
+ "baw.models.ApiBase",
"conf.paths",
"Authenticator",
"$url",
- function (associations, paths, Authenticator, url) {
+ function (ApiBase, paths, Authenticator, url) {
function Tag(resourceOrNewTag) {
@@ -95,7 +95,7 @@ angular
return new Tag(value);
};
- Tag.makeFromApi = associations.makeFromApi(Tag);
+ Tag.makeFromApi = ApiBase.makeFromApiWithType(Tag);
return Tag;
}]);
diff --git a/src/components/models/tag.spec.js b/src/components/models/tag.spec.js
index dcb3ea60..429a578a 100644
--- a/src/components/models/tag.spec.js
+++ b/src/components/models/tag.spec.js
@@ -8,13 +8,25 @@ describe("The Tag object", function () {
"creatorId": 7,
"id": 1,
"isTaxanomic": false,
- "notes": null,
+ "notes": {},
"retired": false,
"text": "Corvus Orru",
"typeOfTag": "species_name",
"updatedAt": "2013-11-20T13:19:13Z",
"updaterId": 7
};
+ var resourceTypes = {
+ "createdAt": Date,
+ "creatorId": Number,
+ "id": Number,
+ "isTaxanomic": Boolean,
+ "notes": Object,
+ "retired": Boolean,
+ "text": String,
+ "typeOfTag": String,
+ "updatedAt": Date,
+ "updaterId": Number
+ };
beforeEach(module("bawApp.models", "rails"));
@@ -46,9 +58,13 @@ describe("The Tag object", function () {
expect(f).toThrow();
});
- it("should expose all the resource with its own api", function () {
- expect(existingTag).toImplement(resource);
- expect(newTag).toImplement(resource);
+ it("should expose all the resource with its own api - with an existing resource", function () {
+ expect(existingTag).toImplement(resourceTypes);
+ });
+
+ // jasmineMatchers' toImplement current does not support testing for fields with null values
+ xit("should expose all the resource with its own api - with a new resource", function () {
+ expect(newTag).toImplement(resourceTypes);
});
var dateFields = [
diff --git a/src/components/models/userProfile.js b/src/components/models/userProfile.js
index 4557190c..a4dcc351 100644
--- a/src/components/models/userProfile.js
+++ b/src/components/models/userProfile.js
@@ -6,17 +6,14 @@ angular
"baw.models.associations",
"baw.models.ApiBase",
"conf.paths",
+ "conf.constants",
"$url",
- function (associations, ApiBase, paths, $url) {
+ function (associations, ApiBase, paths, constants, $url) {
class UserProfile extends ApiBase {
- constructor(resource, defaultProfile) {
- if (!defaultProfile) {
- throw new Error("A default profile must be supplied");
- }
-
+ constructor(resource) {
if (!resource) {
- resource = defaultProfile;
+ resource = constants.defaultProfile;
}
super(resource);
@@ -24,7 +21,7 @@ angular
this.preferences = this.preferences || {};
// ensure preferences are always updated
- this.preferences = Object.assign({}, defaultProfile.preferences, this.preferences);
+ this.preferences = Object.assign({}, constants.defaultProfile.preferences, this.preferences);
this.imageUrls = this.imageUrls.reduce((s, c) => {
c.url = paths.api.root + c.url;
diff --git a/src/components/services/analysisJob.js b/src/components/services/analysisJob.js
new file mode 100644
index 00000000..bf23f5f8
--- /dev/null
+++ b/src/components/services/analysisJob.js
@@ -0,0 +1,169 @@
+angular
+ .module("bawApp.services.analysisJob", [])
+ .factory(
+ "AnalysisJob",
+ [
+ "$resource",
+ "bawResource",
+ "$http",
+ "$q",
+ "conf.paths",
+ "lodash",
+ "casingTransformers",
+ "QueryBuilder",
+ "baw.models.AnalysisJob",
+ function ($resource, bawResource, $http, $q, paths, _, casingTransformers, QueryBuilder, AnalysisJobModel) {
+
+ // FAKED!
+ let fakedData = [
+ {
+ "id": 11111,
+ "name": "fake 11111 new fake",
+ "annotation_name": null,
+ "custom_settings": "#custom settings 267",
+ "script_id": 1,
+ "creator_id": 144,
+ "updater_id": 144,
+ "deleter_id": null,
+ "deleted_at": null,
+ "created_at": "2016-01-18 06:03:10.047508",
+ "updated_at": "2016-02-01 06:03:10.093619",
+ "description": null,
+ "saved_search_id": 1,
+ "started_at": "2016-02-18 06:03:10.028024",
+ "overall_status": "new",
+ "overall_status_modified_at": "2016-02-18 06:03:10.028276",
+ "overall_progress": {},
+ "overall_progress_modified_at": "2016-02-18 06:03:10.028776",
+ "overall_count": 66,
+ "overall_duration_seconds": 6600
+
+ },
+ {
+ "id": 22222,
+ "name": "fake 22222 fake fake 22222 fake fake 22222 fake ",
+ "annotation_name": null,
+ "custom_settings": "#custom settings 267",
+ "script_id": 1,
+ "creator_id": 9,
+ "updater_id": 144,
+ "deleter_id": null,
+ "deleted_at": null,
+ "created_at": "2016-01-18 06:03:10.047508",
+ "updated_at": "2016-02-01 06:03:10.093619",
+ "description": null,
+ "saved_search_id": 1,
+ "started_at": "2016-02-18 06:03:10.028024",
+ "overall_status": "preparing",
+ "overall_status_modified_at": "2016-02-18 06:03:10.028276",
+ "overall_progress": {},
+ "overall_progress_modified_at": "2016-02-18 06:03:10.028776",
+ "overall_count": 77,
+ "overall_duration_seconds": 77700
+
+ },
+ {
+ "id": 1,
+ "name": "\"simulate work analysis\" run on the \"All sites in SERF Acoustic Study\" data",
+ "annotation_name": null,
+ "custom_settings": "#custom settings 267",
+ "script_id": 1,
+ "creator_id": 9,
+ "updater_id": 9,
+ "deleter_id": null,
+ "deleted_at": null,
+ "created_at": "2016-02-18 06:03:10.047508",
+ "updated_at": "2016-02-18 06:03:10.093619",
+ "description": null,
+ "saved_search_id": 1,
+ "started_at": "2016-02-18 06:03:10.028024",
+ "overall_status": "processing",
+ "overall_status_modified_at": "2016-02-18 06:03:10.028276",
+ "overall_progress": {"queued": 10, "working": 1, "successful": 35, "failed": 4, "total": 0},
+ "overall_progress_modified_at": (new Date()).setMinutes(0, 0, 0),
+ "overall_count": 50,
+ "overall_duration_seconds": 88888,
+ "overall_size_bytes": 123456789
+ },
+ {
+ "id": 3600,
+ "name": "fake 3600 completed fake",
+ "annotation_name": null,
+ "custom_settings": "#custom settings 267",
+ "script_id": 1,
+ "creator_id": 9,
+ "updater_id": 144,
+ "deleter_id": null,
+ "deleted_at": null,
+ "created_at": "2016-01-18 06:03:10.047508",
+ "updated_at": "2016-02-01 06:03:10.093619",
+ "description": null,
+ "saved_search_id": 1,
+ "started_at": "2016-02-18 06:03:10.028024",
+ "overall_status": "completed",
+ "overall_status_modified_at": "2016-02-18 06:03:10.028276",
+ "overall_progress": {"queued": 0, "working": 0, "successful": 90, "failed": 10, "total": 100},
+ "overall_progress_modified_at": "2016-02-18 06:03:10.028776",
+ "overall_count": 100,
+ "overall_duration_seconds": 100 * 3600 * 2,
+ "overall_size_bytes": 123456789
+
+ },
+
+ {
+ "id": 99999,
+ "name": "fake 99999 suspended fake",
+ "annotation_name": null,
+ "custom_settings": "#custom settings 267",
+ "script_id": 1,
+ "creator_id": 9,
+ "updater_id": 144,
+ "deleter_id": null,
+ "deleted_at": null,
+ "created_at": "2016-01-18 06:03:10.047508",
+ "updated_at": "2016-02-01 06:03:10.093619",
+ "description": null,
+ "saved_search_id": 1,
+ "started_at": "2016-02-18 06:03:10.028024",
+ "overall_status": "suspended",
+ "overall_status_modified_at": "2016-02-18 06:03:10.028276",
+ "overall_progress": {"queued": 10, "working": 0, "successful": 80, "failed": 10, "total": 100},
+ "overall_progress_modified_at": "2016-02-18 06:03:10.028776",
+ "overall_count": 99,
+ "overall_duration_seconds": 99999
+
+ }
+
+ ];
+ fakedData = casingTransformers.transformObject(fakedData, casingTransformers.camelize);
+
+ function query() {
+ //const path = paths.api.routes.analysisResults;
+ return $q.when({data: {data: fakedData}})
+ .then(x => AnalysisJobModel.makeFromApi(x));
+ }
+
+ function get(id) {
+ return $q.when({data: {data: fakedData.find(x => x.id === id)}})
+ .then(x => AnalysisJobModel.makeFromApi(x));
+ }
+
+ function getName(id) {
+ let fake = fakedData.find(x => x.id === id);
+ return $q.when({
+ data: {
+ data: {
+ id: fake.id,
+ name: fake.name
+ }
+ }
+ })
+ .then(x => AnalysisJobModel.makeFromApi(x));
+ }
+
+ return {
+ query,
+ get,
+ getName
+ };
+ }]);
diff --git a/src/components/services/analysisResult/analysisResult.js b/src/components/services/analysisResult/analysisResult.js
index 8a0b109f..3fa4adc7 100644
--- a/src/components/services/analysisResult/analysisResult.js
+++ b/src/components/services/analysisResult/analysisResult.js
@@ -1,16 +1,496 @@
angular
.module("bawApp.services.analysisResult", [])
.factory(
- "AnalysisResult",
- [
- "$http",
- function ($http) {
- var analysisResult = {};
+ "AnalysisResult",
+ [
+ "$resource",
+ "bawResource",
+ "$http",
+ "$q",
+ "conf.paths",
+ "lodash",
+ "casingTransformers",
+ "QueryBuilder",
+ "baw.models.AnalysisResult",
+ function ($resource,
+ bawResource,
+ $http,
+ $q,
+ paths,
+ _,
+ casingTransformers,
+ QueryBuilder,
+ AnalysisResultModel) {
+ // FAKED!
+ let fakedData = [
+ {
+ "analysis_job_id": 1,
+ "audio_recording_id": null,
+ "path": "/",
+ "name": "/",
+ "type": "directory",
+ "has_zip": false,
+ "children": [
+ 1234,
+ 123456,
+ 124124,
+ 234234,
+ ...(new Array(1000)).fill(1).map((x, i) => i)
+ ].map(x => ({
+ name: x.toString(),
+ path: "/" + x.toString(),
+ type: "directory",
+ "has_children": true,
+ has_zip: false
+ }))
+ },
+ {
+ "analysis_job_id": 1,
+ "audio_recording_id": 1234,
+ "path": "/1234",
+ "name": "1234",
+ "type": "directory",
+ "has_zip": true,
+ "children": [
+ {
+ "name": "log.txt",
+ "type": "file",
+ "size_bytes": 196054,
+ "mime": "text/plain"
+ },
+ {
- return analysisResult;
- }
- ]
-);
\ No newline at end of file
+ "name": "Towsey.Acoustic.yml",
+ "type": "file",
+ "size_bytes": 1968,
+ "mime": "application/x-yaml"
+ },
+ {
+ "path": "/1234/Towsey.Acoustic",
+ "name": "Towsey.Acoustic",
+ "type": "directory",
+ "has_children": true
+ }
+ ]
+
+ },
+ {
+ "analysis_job_id": 1,
+ "audio_recording_id": 1234,
+ "path": "/1234/Towsey.Acoustic/Hello/test/bigtest/test.txt",
+ "name": "test.txt",
+ "type": "directory",
+ "children": []
+ },
+ {
+ "analysis_job_id": 1,
+ "audio_recording_id": 1234,
+ "path": "/1234/Towsey.Acoustic/ZoomingTiles",
+ "name": "ZoomingTiles",
+ "type": "directory",
+ "has_zip": false,
+ "children": []
+ },
+ {
+
+ "analysis_job_id": 1,
+ "audio_recording_id": 1234,
+ "path": "/1234/Towsey.Acoustic",
+ "name": "Towsey.Acoustic",
+ "type": "directory",
+ "has_zip": true,
+ "children": [
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__IndexGenerationData.json",
+ "type": "file",
+ "size_bytes": 217,
+ "mime": "application/json"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__IndexStatistics.json",
+ "type": "file",
+ "size_bytes": 12549,
+ "mime": "application/json"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__IndexDistributions.png",
+ "type": "file",
+ "size_bytes": 21726,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI.png",
+ "type": "file",
+ "size_bytes": 394539,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN.png",
+ "type": "file",
+ "size_bytes": 271785,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__CVR.png",
+ "type": "file",
+ "size_bytes": 406413,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__DIF.png",
+ "type": "file",
+ "size_bytes": 10799,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ENT.png",
+ "type": "file",
+ "size_bytes": 397892,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__EVN.png",
+ "type": "file",
+ "size_bytes": 420567,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__SUM.png",
+ "type": "file",
+ "size_bytes": 6001,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__POW.png",
+ "type": "file",
+ "size_bytes": 386530,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__SPT.png",
+ "type": "file",
+ "size_bytes": 332683,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN-POW-CVR.png",
+ "type": "file",
+ "size_bytes": 830777,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.png",
+ "type": "file",
+ "size_bytes": 954437,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__2Maps.png",
+ "type": "file",
+ "size_bytes": 1555863,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN-POW-CVR.SummaryRibbon.png",
+ "type": "file",
+ "size_bytes": 5367,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.SummaryRibbon.png",
+ "type": "file",
+ "size_bytes": 5486,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN-POW-CVR.SpectralRibbon.png",
+ "type": "file",
+ "size_bytes": 105946,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.SpectralRibbon.png",
+ "type": "file",
+ "size_bytes": 121949,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-140000Z_60.png",
+ "type": "file",
+ "size_bytes": 40594,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-150000Z_60.png",
+ "type": "file",
+ "size_bytes": 38652,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-160000Z_60.png",
+ "type": "file",
+ "size_bytes": 38851,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-170000Z_60.png",
+ "type": "file",
+ "size_bytes": 35306,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-180000Z_60.png",
+ "type": "file",
+ "size_bytes": 38927,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-190000Z_60.png",
+ "type": "file",
+ "size_bytes": 48092,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-200000Z_60.png",
+ "type": "file",
+ "size_bytes": 45757,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-210000Z_60.png",
+ "type": "file",
+ "size_bytes": 41888,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-220000Z_60.png",
+ "type": "file",
+ "size_bytes": 43857,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-230000Z_60.png",
+ "type": "file",
+ "size_bytes": 45503,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-000000Z_60.png",
+ "type": "file",
+ "size_bytes": 40867,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-010000Z_60.png",
+ "type": "file",
+ "size_bytes": 41826,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-020000Z_60.png",
+ "type": "file",
+ "size_bytes": 44875,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-030000Z_60.png",
+ "type": "file",
+ "size_bytes": 42020,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-040000Z_60.png",
+ "type": "file",
+ "size_bytes": 42820,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-050000Z_60.png",
+ "type": "file",
+ "size_bytes": 38260,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-060000Z_60.png",
+ "type": "file",
+ "size_bytes": 41557,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-070000Z_60.png",
+ "type": "file",
+ "size_bytes": 42659,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-080000Z_60.png",
+ "type": "file",
+ "size_bytes": 34909,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-090000Z_60.png",
+ "type": "file",
+ "size_bytes": 31175,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-100000Z_60.png",
+ "type": "file",
+ "size_bytes": 29885,
+ "mime": "image/png"
+ },
+ {
+ "path": "/1234/Towsey.Acoustic/ZoomingTiles",
+ "name": "ZoomingTiles",
+ "type": "directory",
+ "has_children": true
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-110000Z_60.png",
+ "type": "file",
+ "size_bytes": 29891,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-120000Z_60.png",
+ "type": "file",
+ "size_bytes": 29532,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-130000Z_60.png",
+ "type": "file",
+ "size_bytes": 24295,
+ "mime": "image/png"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.Indices.csv",
+ "type": "file",
+ "size_bytes": 452247,
+ "mime": "text/csv"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.Indices_BACKUP.csv",
+ "type": "file",
+ "size_bytes": 452247,
+ "mime": "text/csv"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.ACI.csv",
+ "type": "file",
+ "size_bytes": 6581066,
+ "mime": "text/csv"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.BGN.csv",
+ "type": "file",
+ "size_bytes": 6581465,
+ "mime": "text/csv"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.CVR.csv",
+ "type": "file",
+ "size_bytes": 6821263,
+ "mime": "text/csv"
+ },
+ {
+
+ "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.DIF.csv",
+ "type": "file",
+ "size_bytes": 6213756,
+ "mime": "text/csv"
+ }
+ ]
+ }
+ ];
+ fakedData = casingTransformers.transformObject(fakedData, casingTransformers.camelize);
+
+ function query() {
+ //const path = paths.api.routes.analysisResults;
+ return $q.when({data: {data: fakedData}})
+ .then(x => AnalysisResultModel.makeFromApi(x));
+ }
+
+ function get(path, page = 1) {
+
+ let data = angular.copy({data: fakedData.find(x => x.path === path), meta:{}});
+ let length = (data.data.children || []).length;
+ if (length > 100) {
+ let offset = (page - 1) * 100;
+ data.meta = {
+ paging: {
+ "page": page,
+ "items": 100,
+ "total": length,
+ "maxPage": Math.ceil(length / 100),
+ "current": null,
+ "previous": null,
+ "next": null
+ }
+ };
+ data.data.children = data.data.children.slice(offset, offset + 100);
+ }
+
+ return $q.when({data})
+ .then(x => AnalysisResultModel.makeFromApi(x));
+ }
+
+ return {
+ query,
+ get
+ };
+ }
+ ]
+ );
diff --git a/src/components/services/audioEvent.js b/src/components/services/audioEvent.js
index eac8dc8d..af2e2098 100644
--- a/src/components/services/audioEvent.js
+++ b/src/components/services/audioEvent.js
@@ -77,7 +77,7 @@ angular
return q;
});
- return $http.post(url, qb.toJSON());
+ return $http.post(url, qb.toJSONString());
};
const filterUrl = paths.api.routes.audioEvent.filterAbsolute;
@@ -87,7 +87,7 @@ angular
});
return $http
- .post(filterUrl, query.toJSON())
+ .post(filterUrl, query.toJSONString())
.then(x => AudioEventModel.makeFromApi(x));
};
@@ -100,7 +100,7 @@ angular
);
});
- return $http.post(filterUrl, query.toJSON())
+ return $http.post(filterUrl, query.toJSONString())
.then(resultPager.loadAll)
.then(x => AudioEventModel.makeFromApi(x));
};
diff --git a/src/components/services/audioRecording.js b/src/components/services/audioRecording.js
index 072c95c5..153f0fc6 100644
--- a/src/components/services/audioRecording.js
+++ b/src/components/services/audioRecording.js
@@ -30,7 +30,7 @@ angular
resource.getRecentRecordings = function () {
- return $http.post(filterUrl, query.toJSON());
+ return $http.post(filterUrl, query.toJSONString());
};
resource.getRecordingsForVisualization = function (siteIds) {
@@ -45,7 +45,7 @@ angular
});
return $http
- .post(filterUrl, query.toJSON())
+ .post(filterUrl, query.toJSONString())
.then(x => AudioRecordingCore.makeFromApi(x));
};
@@ -55,7 +55,7 @@ angular
.project({include: ["id", "siteId", "durationSeconds", "recordedDate"]}));
return $http
- .post(filterUrl, query.toJSON())
+ .post(filterUrl, query.toJSONString())
.then(x => AudioRecordingModel.makeFromApi(x));
};
return resource;
diff --git a/src/components/services/bawResource.spec.js b/src/components/services/bawResource.spec.js
index ee59e88f..48cdb255 100644
--- a/src/components/services/bawResource.spec.js
+++ b/src/components/services/bawResource.spec.js
@@ -14,17 +14,17 @@ describe("The bawResource service", function () {
$rootScope = _$rootScope;
}]));
-
- it("should return a resource constructor that includes update/put", function () {
-
- expect(bawResource("/test")).toImplement({
- "get": null,
- "save": null,
- "query": null,
- "remove": null,
- "delete": null,
- "update": null,
- "modifiedPath": null
+ // jasmineMatchers' toImplement currently does not support testing for fields on Function objects
+ xit("should return a resource constructor that includes update/put", function () {
+ var resource = bawResource("/test");
+ expect(resource).toImplement({
+ "get": Function,
+ "save": Function,
+ "query": Function,
+ "remove": Function,
+ "delete": Function,
+ "update": Function,
+ "modifiedPath": String
});
});
diff --git a/src/components/services/bookmark.spec.js b/src/components/services/bookmark.spec.js
index c4e99b17..345ddb97 100644
--- a/src/components/services/bookmark.spec.js
+++ b/src/components/services/bookmark.spec.js
@@ -13,9 +13,9 @@ describe("The bookmark service", function () {
it("will return a promise for retrieving application bookmarks", function() {
expect(bawResource.applicationBookmarksPromise).toImplement({
- catch: null,
- finally: null,
- then: null
+ catch: Function,
+ finally: Function,
+ then: Function
});
});
});
\ No newline at end of file
diff --git a/src/components/services/mime.js b/src/components/services/mime.js
new file mode 100644
index 00000000..7e19abde
--- /dev/null
+++ b/src/components/services/mime.js
@@ -0,0 +1,74 @@
+angular
+ .module("bawApp.services.mime", [])
+ .service(
+ "MimeType",
+ [
+ function () {
+ let make = (name, icon, mimeTypes, extensions) => ({extensions, icon, mimeTypes, name});
+
+ let mimeTypes = [
+ make("png", "fa-file-image-o", ["image/png"], ["png"]),
+ make("jpeg", "fa-file-image-o", ["image/jpeg"], ["jpeg", "jpg", "jpe", "pjpeg"]),
+ make("gif", "fa-file-image-o", ["image/gif"], ["gif"]),
+ make("bitmap", "fa-file-image-o", ["image/bitmap"], ["bmp"]),
+ make("wave", "fa-file-audio-o", [
+ "audio/wav",
+ "audio/x-wav",
+ "audio/wave",
+ "audio/x-pn-wav"], ["wav"]),
+ make("mp3", "fa-file-audio-o", ["audio/mpeg3", "audio/x-mpeg-3"], ["mp3"]),
+ make("csv", "fa-file-excel-o", ["text/csv"], ["csv"]),
+ make("html", "fa-file-code-o", ["text/html", "application/xhtml+xml"], ["html", "xhtml"]),
+ make("plain", "fa-file-text-o", ["text/plain"], ["txt"]),
+ make("yaml", "fa-file-code-o", ["application/x-yaml", "text/yaml"], ["yaml", "yml"]),
+ make("xml", "fa-file-code-o", ["application/x-xml", "application/xml", "text/xml"], ["xml"]),
+ make("json", "fa-file-code-o", [
+ "application/x-json",
+ "application/json",
+ "text/json",
+ "text/x-json"], ["json"]),
+ make("pdf", "fa-file-pdf-o", ["application/pdf"], ["pdf"]),
+ make("zip", "fa-file-archive-o", ["application/zip"], ["zip"]),
+ make("gzip", "fa-file-archive-o", ["application/gzip", "application/x-gzip"], ["gz", "gzip"]),
+ make("binary", "fa fa-file-o", ["application/octet-stream"], null),
+ make("unknown", "fa fa-file-o", ["application/unknown"], null)
+ ];
+
+
+ let reverseLookup = new Map(
+ mimeTypes.reduce(
+ (rest, m) => rest.concat(
+ m.mimeTypes.map(mt => [mt, m])
+ ),
+ []
+ )
+ );
+
+
+ function mimeToMode(mimeType) {
+ if (reverseLookup.has(mimeType)) {
+ return reverseLookup.get(mimeType).name;
+ }
+ else {
+ return null;
+ }
+ }
+
+ function mimeToFaIcon(mimeType) {
+ if (reverseLookup.has(mimeType)) {
+ return "fa " + reverseLookup.get(mimeType).icon;
+ }
+ else {
+ return "fa fa fa-file-o";
+ }
+ }
+
+ return {
+ mimeTypes,
+ mimeToMode,
+ mimeToFaIcon
+ };
+
+ }
+ ]
+ );
diff --git a/src/components/services/predictiveCache.spec.js b/src/components/services/predictiveCache.spec.js
index 55915729..320d2e78 100644
--- a/src/components/services/predictiveCache.spec.js
+++ b/src/components/services/predictiveCache.spec.js
@@ -57,8 +57,8 @@ describe("The predictiveCache service", function () {
it("ensure the interceptor implements the expected methods", function () {
expect(predictiveCacheInterceptor).toImplement({
- response: null,
- listeners: null
+ response: Function,
+ listeners: Function
});
});
diff --git a/src/components/services/project.js b/src/components/services/project.js
index 9af6d0a4..61e0da55 100644
--- a/src/components/services/project.js
+++ b/src/components/services/project.js
@@ -26,13 +26,15 @@ angular
{projectId: "@projectId"}
);
- var gapUrl = paths.api.routes.project.filterAbsolute;
- var gapQuery = QueryBuilder.create(function (q) {
- return q.project({"include": ["id", "name"]});
- });
- resource.getAllProjects = function () {
+
+ resource.getAllProjectNames = function () {
+ const gapUrl = paths.api.routes.project.filterAbsolute;
+ const gapQuery = QueryBuilder.create(function (q) {
+ return q.project({"include": ["id", "name"]});
+ });
+
return $http
- .post(gapUrl, gapQuery.toJSON())
+ .post(gapUrl, gapQuery.toJSONString())
.then(x => ProjectModel.makeFromApi(x));
};
@@ -47,7 +49,7 @@ angular
});
return $http
- .post(gpbiUrl, query.toJSON())
+ .post(gpbiUrl, query.toJSONString())
.then(x => ProjectModel.makeFromApi(x));
};
@@ -61,7 +63,7 @@ angular
});
return $http
- .post(gpbsiUrl, query.toJSON())
+ .post(gpbsiUrl, query.toJSONString())
.then(x => ProjectModel.makeFromApi(x));
};
diff --git a/src/components/services/queryBuilder.js b/src/components/services/queryBuilder.js
index 77ba9233..c911e5ef 100644
--- a/src/components/services/queryBuilder.js
+++ b/src/components/services/queryBuilder.js
@@ -417,7 +417,7 @@ angular
return this.end.call(query);
};
- this.toJSON = function toJSON(spaces) {
+ this.toJSON = function() {
var compiledQuery = {},
that = this;
@@ -427,7 +427,11 @@ angular
}
});
- return JSON.stringify(compiledQuery, null, spaces);
+ return compiledQuery;
+ };
+
+ this.toJSONString = function toJSON(spaces) {
+ return JSON.stringify(this.toJSON(), null, spaces);
};
this.toQueryString = function toQueryString() {
diff --git a/src/components/services/queryBuilder.spec.js b/src/components/services/queryBuilder.spec.js
index 1f9e262e..894589d8 100644
--- a/src/components/services/queryBuilder.spec.js
+++ b/src/components/services/queryBuilder.spec.js
@@ -56,7 +56,8 @@ describe("The QueryBuilder", function () {
}).toThrowError(Error, "The create callback must return a child instance of Query passed to the callback");
});
- it("should implement the expected interface", function () {
+ // jasmineMatchers' toImplement current does not support testing for fields with null values
+ xit("should implement the expected interface", function () {
var queryInterface = validCombinators.concat(validOperators);
var rootInterface = queryInterface.concat(rootOperators);
@@ -105,7 +106,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.or(q.eq("field", 3.0), q.lt("field", 6.0)));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("will gracefully resolve key conflicts for combinators in the deep merge - test 2", function() {
@@ -132,7 +133,7 @@ describe("The QueryBuilder", function () {
return q;
});
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("a query operator should return a new instance of a Query", function () {
@@ -169,7 +170,7 @@ describe("The QueryBuilder", function () {
it("should be able to produce a bare query", function () {
var expected = {};
- expect(q.toJSON(spaces)).toBe(j(expected));
+ expect(q.toJSONString(spaces)).toBe(j(expected));
});
@@ -186,7 +187,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.and(q.eq("field", 3.0)));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should ensure .end and .compose and .create are the same", function () {
@@ -205,16 +206,16 @@ describe("The QueryBuilder", function () {
};
var actualCompose = q.compose(q.eq("fieldA", 3.0).or(q.field("fieldB").lt(6.0).gt(3.0)));
- expect(actualCompose.toJSON(spaces)).toBe(j(expected));
+ expect(actualCompose.toJSONString(spaces)).toBe(j(expected));
q = queryBuilder.create();
var actualEnd = q.eq("fieldA", 3.0).or(q.field("fieldB").lt(6.0).gt(3.0)).end();
- expect(actualEnd.toJSON(spaces)).toBe(j(expected));
+ expect(actualEnd.toJSONString(spaces)).toBe(j(expected));
var actualCreate = queryBuilder.create(function (q) {
return q.eq("fieldA", 3.0).or(q.field("fieldB").lt(6.0).gt(3.0));
});
- expect(actualCreate.toJSON(spaces)).toBe(j(expected));
+ expect(actualCreate.toJSONString(spaces)).toBe(j(expected));
});
it("should ensure not is arity:1 only", function () {
@@ -234,7 +235,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.eq("field", 3.0));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should allow more than one operation at root level", function () {
@@ -251,7 +252,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.eq("fieldA", 3.0).eq("fieldB", 6.0));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("ensures that the in function only takes an array", function () {
@@ -275,7 +276,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.in("fieldA", new Set([1, 2, 3, 3])));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("ensures that the in function automatically does a uniqueness check", function () {
@@ -289,7 +290,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.in("fieldA", [1, 2, 3, 3]));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("ensures the regex function is not supported", function () {
@@ -315,7 +316,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q[rangeFunction]("fieldA", "(3,20.0)"));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
expected.filter.fieldA[rangeFunction] = {
from: 3,
@@ -325,7 +326,7 @@ describe("The QueryBuilder", function () {
q = queryBuilder.create();
actual = q.compose(q[rangeFunction]("fieldA", {from: 3, to: 20.0}));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it(rangeFunction + " it validates a string range", function () {
@@ -399,7 +400,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q[rangeFunction]("fieldA", {from: 20, to: null}));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
expected = {
filter: {
@@ -414,7 +415,7 @@ describe("The QueryBuilder", function () {
q = queryBuilder.create();
actual = q.compose(q[rangeFunction]("fieldA", {from: null, to: 20}));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
expected = {
};
@@ -422,7 +423,7 @@ describe("The QueryBuilder", function () {
q = queryBuilder.create();
actual = q.compose(q[rangeFunction]("fieldA", {from: null, to: null}));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
});
@@ -436,11 +437,11 @@ describe("The QueryBuilder", function () {
};
var actual = q.compose(q.range("fieldA", "(,20.0)"));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
q = queryBuilder.create();
actual = q.compose(q.range("fieldA", {to: 20}));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
expected = {
filter: {
@@ -452,7 +453,7 @@ describe("The QueryBuilder", function () {
q = queryBuilder.create();
actual = q.compose(q.range("fieldA", "(,20.0]"));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("the smart range function simplifies a missing upper bound", function () {
@@ -465,7 +466,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.compose(q.range("fieldA", "(3,)"));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
expected = {
@@ -478,11 +479,11 @@ describe("The QueryBuilder", function () {
q = queryBuilder.create();
actual = q.compose(q.range("fieldA", "[3,)"));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
q = queryBuilder.create();
actual = q.compose(q.range("fieldA", {from: 3}));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
});
@@ -508,7 +509,7 @@ describe("The QueryBuilder", function () {
var actual = q.compose(q.and(
q.lt("fieldA", 3.0).contains("fieldB", "hello").gt("fieldA", 0.0)
).lt("fieldC", 17));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should handle a more complex query - with separate queries for the same field", function () {
@@ -530,7 +531,7 @@ describe("The QueryBuilder", function () {
q.lt("fieldA", 3.0).contains("fieldB", "hello"),
q.gt("fieldA", 0.0)
));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should handle a more complex query - with lots of nesting", function () {
@@ -578,7 +579,7 @@ describe("The QueryBuilder", function () {
)
)
));
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should allow paging to be set", function () {
@@ -590,7 +591,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.page({items: 10, page: 30});
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should validate page arguments", function () {
@@ -619,7 +620,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.page.disable();
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
@@ -632,7 +633,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.page({}).page.disable();
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should should allow re-enabling of paging", function () {
@@ -645,7 +646,7 @@ describe("The QueryBuilder", function () {
// this essentially represents paging with the default options
var actual = q.page.disable().page({});
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
@@ -663,7 +664,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.eq("fieldA", 30).page({items: 5, page: 2}).end();
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should always update root with paging...even if on a subquery (for disablePaging too)", function () {
@@ -679,7 +680,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.eq("fieldA", 30).page.disable().end();
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should allow sorting to be set", function () {
@@ -691,7 +692,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.sort({orderBy: "durationSeconds", direction: "desc"});
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should validate sorting arguments", function () {
@@ -730,7 +731,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.lt("fieldB", 6.0).sort({orderBy: "durationSeconds"}).end();
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should allow projection to be set (whitelist)", function () {
@@ -743,7 +744,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.project({include: ["durationSeconds", "id"]});
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
@@ -756,7 +757,7 @@ describe("The QueryBuilder", function () {
}
};
var actual = q.project({exclude: ["durationSeconds", "id"]});
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
it("should validate projection arguments", function () {
@@ -795,7 +796,7 @@ describe("The QueryBuilder", function () {
};
var actual = q.notEq("fieldC", 7.5).project({include: ["durationSeconds", "fieldC"]}).end();
- expect(actual.toJSON(spaces)).toBe(j(expected));
+ expect(actual.toJSONString(spaces)).toBe(j(expected));
});
@@ -917,7 +918,7 @@ describe("The QueryBuilder", function () {
{page: 1, items: 10}
).end();
- expect(actual.toJSON(spaces)).toBe(j(complexExpected));
+ expect(actual.toJSONString(spaces)).toBe(j(complexExpected));
});
it("should be able to load a very complex query", function () {
diff --git a/src/components/services/savedSearch.js b/src/components/services/savedSearch.js
new file mode 100644
index 00000000..345b93bd
--- /dev/null
+++ b/src/components/services/savedSearch.js
@@ -0,0 +1,58 @@
+angular
+ .module("bawApp.services.savedSearch", [])
+ .factory(
+ "SavedSearch",
+ [
+ "$resource",
+ "bawResource",
+ "$http",
+ "$q",
+ "conf.paths",
+ "lodash",
+ "casingTransformers",
+ "QueryBuilder",
+ "baw.models.SavedSearch",
+ function ($resource, bawResource, $http, $q, paths, _, casingTransformers, QueryBuilder, SavedSearchModel) {
+
+ // FAKED!
+ let fakedData = [
+
+ {
+ "id": 1,
+ "name": "test saved search - SERF",
+ "description": "I'm a description and that's ok",
+ "stored_query":{"siteId":{"in":[398, 401, 399, 402, 400, 508 ] }},
+ "creator_id": 9,
+ "created_at": "2016-02-18T15:21:45.862+10:00",
+ "project_ids":[397, 469],
+ "analysis_job_ids":[1]
+ },
+ {
+ "id": 2,
+ "name": "FAKE DATA test saved search - SERFishg",
+ "description": "I'm a description and that's ok ALA LA la La al LAA",
+ "stored_query":{"siteId":{"in":[398, 754 ] }},
+ "creator_id": 144,
+ "created_at": "2016-03-01T15:21:45.862+10:00",
+ "project_ids":[397, 645],
+ "analysis_job_ids":[]
+ }
+ ];
+ fakedData = casingTransformers.transformObject(fakedData, casingTransformers.camelize);
+
+ function query() {
+ //const path = paths.api.routes.analysisResults;
+ return $q.when({data: {data: fakedData}})
+ .then(x => SavedSearchModel.makeFromApi(x));
+ }
+
+ function get(id) {
+ return $q.when({data: {data: fakedData.find(x => x.id === id)}})
+ .then(x => SavedSearchModel.makeFromApi(x));
+ }
+
+ return {
+ query,
+ get
+ };
+ }]);
diff --git a/src/components/services/scripts.js b/src/components/services/scripts.js
new file mode 100644
index 00000000..1bbfe361
--- /dev/null
+++ b/src/components/services/scripts.js
@@ -0,0 +1,60 @@
+angular
+ .module("bawApp.services.script", [])
+ .factory(
+ "Script",
+ [
+ "$resource",
+ "bawResource",
+ "$http",
+ "$q",
+ "conf.paths",
+ "lodash",
+ "casingTransformers",
+ "QueryBuilder",
+ "baw.models.Script",
+ function ($resource, bawResource, $http, $q, paths, _, casingTransformers, QueryBuilder, ScriptModel) {
+
+ // FAKED!
+ let fakedData = [
+
+ {
+ "id": 1,
+ "name": "simulate work",
+ "description": "simulates running an analysis",
+ "analysis_identifier": "SIMULATE_WORK",
+ "version": 1,
+ "creator_id": 1,
+ "created_at": "2016-02-18T15:58:05.465+10:00",
+ "executable_settings": `{\n\t"hellllo": "test"\n}`,
+ "executable_settings_media_type": "application/json"
+ },
+ {
+ "id": 2,
+ "name": "simulate work VERSION 2",
+ "description": "simulates running an analysis",
+ "analysis_identifier": "SIMULATE_WORK",
+ "version": 2,
+ "creator_id": 1,
+ "created_at": "2016-02-18T15:58:05.465+10:00",
+ "executable_settings": "---\nAnalysisName: Towsey.KoalaMale\n# min and max of the freq band to search\nMinHz: 250 \nMaxHz: 800\n# duration of DCT in seconds \n# this cannot be too long because the oscillations are not constant.\nDctDuration: 0.30\n# minimum acceptable amplitude of a DCT coefficient\nDctThreshold: 0.5\n# ignore oscillation rates below the min & above the max threshold\n# OSCILLATIONS PER SECOND\nMinOcilFreq: 20 \nMaxOcilFreq: 55\n# Minimum duration for the length of a true event (seconds).\nMinDuration: 0.5\n# Maximum duration for the length of a true event.\nMaxDuration: 2.5\n# Event threshold - Determines FP \/ FN trade-off for events.\nEventThreshold: 0.2\n################################################################################\nSaveIntermediateWavFiles: false\nSaveIntermediateCsvFiles: false\nSaveSonogramImages: false\nDisplayCsvImage: false\nParallelProcessing: false\n#DoNoiseReduction: true\n#BgNoiseThreshold: 3.0\n\nIndexPropertiesConfig: \".\\\\IndexPropertiesConfig.yml\"\n...",
+ "executable_settings_media_type": "application/x-yaml"
+ }
+ ];
+ fakedData = casingTransformers.transformObject(fakedData, casingTransformers.camelize);
+
+ function query() {
+ //const path = paths.api.routes.analysisResults;
+ return $q.when({data: {data: fakedData}})
+ .then(x => ScriptModel.makeFromApi(x));
+ }
+
+ function get(id) {
+ return $q.when({data: {data: fakedData.find(x => x.id === id)}})
+ .then(x => ScriptModel.makeFromApi(x));
+ }
+
+ return {
+ query,
+ get
+ };
+ }]);
diff --git a/src/components/services/services.js b/src/components/services/services.js
index 18338ec3..5c17017e 100644
--- a/src/components/services/services.js
+++ b/src/components/services/services.js
@@ -15,6 +15,7 @@ angular.module(
"bawApp.services.resultPager",
// endpoint specific
+ "bawApp.services.analysisJob",
"bawApp.services.analysisResult",
"bawApp.services.analysisResultFile",
"bawApp.services.bookmark",
@@ -25,7 +26,10 @@ angular.module(
"bawApp.services.audioEvent",
"bawApp.services.taggings",
"bawApp.services.tag",
+ "bawApp.services.mime",
"bawApp.services.media",
+ "bawApp.services.savedSearch",
+ "bawApp.services.script",
"bawApp.services.birdWalkService",
"bawApp.services.breadcrumbs",
"bawApp.services.userProfile",
diff --git a/src/components/services/site.js b/src/components/services/site.js
index 351c3c43..5cb5c0d4 100644
--- a/src/components/services/site.js
+++ b/src/components/services/site.js
@@ -15,7 +15,7 @@ angular
.sort({orderBy: "name"});
});
return $http
- .post(url, query.toJSON())
+ .post(url, query.toJSONString())
.then( x => SiteModel.makeFromApi(x));
};
@@ -27,7 +27,7 @@ angular
.sort({orderBy: "name"});
});
return $http
- .post(url, query.toJSON())
+ .post(url, query.toJSONString())
.then( x => SiteModel.makeFromApi(x));
};
@@ -38,7 +38,7 @@ angular
.sort({orderBy: "name"});
});
return $http
- .post(url, query.toJSON())
+ .post(url, query.toJSONString())
.then( x => SiteModel.makeFromApi(x));
};
@@ -47,7 +47,7 @@ angular
var query = QueryBuilder.create(function (q) {
return q.project({"include": ["id", "name"]});
});
- return $http.post(url, query.toJSON());
+ return $http.post(url, query.toJSONString());
};*/
diff --git a/src/components/services/tag.js b/src/components/services/tag.js
index 640421e9..92602ce8 100644
--- a/src/components/services/tag.js
+++ b/src/components/services/tag.js
@@ -130,7 +130,7 @@ angular
return q.in("audioEvents.id", audioEventIds);
});
- return $http.post(url, query.toJSON()).then(x => TagModel.makeFromApi(x));
+ return $http.post(url, query.toJSONString()).then(x => TagModel.makeFromApi(x));
};
return resource;
diff --git a/src/components/services/unitConverters.spec.js b/src/components/services/unitConverters.spec.js
index d7c4e952..68464183 100644
--- a/src/components/services/unitConverters.spec.js
+++ b/src/components/services/unitConverters.spec.js
@@ -93,13 +93,13 @@ describe("The unitConverter service", function () {
it("returns an object that implements the required API", function () {
expect(converters).toImplement({
- input: {},
- conversions: {},
- pixelsToSeconds: angular.noop,
- secondsToPixels: angular.noop,
- hertzToPixels: angular.noop,
- invertHertz: angular.noop,
- invertPixels: angular.noop
+ input: Object,
+ conversions: Object,
+ pixelsToSeconds: Function,
+ secondsToPixels: Function,
+ hertzToPixels: Function,
+ invertHertz: Function,
+ invertPixels: Function
});
});
diff --git a/src/components/services/url.js b/src/components/services/url.js
index fc0ea8a9..38f3682a 100644
--- a/src/components/services/url.js
+++ b/src/components/services/url.js
@@ -31,34 +31,93 @@ angular
if (angular.isUndefined(val) || val === null) {
return "";
}
+
return encodeURIComponent(val).
- replace(/%40/gi, "@").
- replace(/%3A/gi, ":").
- replace(/%24/g, "$").
- replace(/%2C/gi, ",").
- replace(/%20/g, (pctEncodeSpaces ? "%20" : "+"));
+ replace(/%40/gi, "@").
+ replace(/%3A/gi, ":").
+ replace(/%24/g, "$").
+ replace(/%2C/gi, ",").
+ replace(/%3B/gi, ";").
+ replace(/%20/g, (pctEncodeSpaces ? "%20" : "+"));
}
+
function toKeyValue(obj, validateKeys, _tokenRenamer) {
var tokenRenamer = _tokenRenamer || _renamerFunc;
var parts = [];
- angular.forEach(obj, function (value, key) {
+ angular.forEach(obj, function(value, key) {
if (validateKeys) {
// only add key value pair if value is not undefined, not null, and is not an empty string
- var valueIsEmptyString = angular.isString(value) && value.length < 1;
- if (angular.isUndefined(value) || value === null || valueIsEmptyString || value === false) {
+ var valueIsEmptyString = value === "";
+ if (value === undefined || value === null || valueIsEmptyString || value === false) {
return;
}
}
- var encodedKey = encodeUriQuery(tokenRenamer(key), /* encode spaces */ true);
+ // apply casing transforms
+ key = tokenRenamer(key);
+
+ // Angular encodes `true` as just the key without a value - like a flag
+ if (angular.isArray(value)) {
+ angular.forEach(value, function(arrayValue) {
+ parts.push(encodeUriQuery(key, true) +
+ (arrayValue === true ? "" : "=" + encodeUriQuery(arrayValue, true)));
+ });
+ } else {
+ parts.push(encodeUriQuery(key, true) +
+ (value === true ? "" : "=" + encodeUriQuery(value, true)));
+ }
+ });
+ return parts.length ? parts.join("&") : "";
+ }
+
+ /**
+ * Tries to decode the URI component without throwing an exception.
+ *
+ * @private
+ * @param str value potential URI component to check.
+ * @returns {boolean} True if `value` can be decoded
+ * with the decodeURIComponent function.
+ */
+ function tryDecodeURIComponent(value) {
+ try {
+ return decodeURIComponent(value);
+ } catch (e) {
+ // Ignore any invalid uri component
+ }
+ }
- // Angular does this: if value is true, just include the key without a value
- var encodedValue = value === true ? "" : "=" + encodeUriQuery(value, /* encode spaces */ true);
- parts.push(encodedKey + encodedValue);
+ /**
+ * Parses an escaped url query string into key-value pairs.
+ * Lifted from https://github.com/angular/angular.js/blob/0ece2d5e0b34a27baa6238c3c2dcb4f92ccfa805/src/Angular.js#L1289
+ * @returns {Object.
}
+ */
+ function parseKeyValue(/**string*/keyValue) {
+ var obj = {};
+ angular.forEach((keyValue || "").split("&"), function(keyValue) {
+ var splitPoint, key, val;
+ if (keyValue) {
+ key = keyValue = keyValue.replace(/\+/g,"%20");
+ splitPoint = keyValue.indexOf("=");
+ if (splitPoint !== -1) {
+ key = keyValue.substring(0, splitPoint);
+ val = keyValue.substring(splitPoint + 1);
+ }
+ key = tryDecodeURIComponent(key);
+ if (angular.isDefined(key)) {
+ val = angular.isDefined(val) ? tryDecodeURIComponent(val) : true;
+ if (!hasOwnProperty.call(obj, key)) {
+ obj[key] = val;
+ } else if (angular.isArray(obj[key])) {
+ obj[key].push(val);
+ } else {
+ obj[key] = [obj[key],val];
+ }
+ }
+ }
});
- return parts.length ? parts.join("&") : "";
+ return obj;
}
function formatUri(uri, values, tokenRenamer) {
@@ -111,7 +170,8 @@ angular
encodeUriQuery,
toKeyValue,
formatUri,
- formatUriFast
+ formatUriFast,
+ parseKeyValue
};
this.registerRenamer = function(suffix, renamerFunc) {
diff --git a/src/components/services/userProfile.js b/src/components/services/userProfile.js
index 31aa5c39..435db0d4 100644
--- a/src/components/services/userProfile.js
+++ b/src/components/services/userProfile.js
@@ -57,13 +57,12 @@ angular
.then(function success(response) {
console.log("User profile loaded");
- exports.profile = (new UserProfileModel(response.data.data,
- constants.defaultProfile));
+ exports.profile = (new UserProfileModel(response.data.data));
return exports.profile;
}, function error(response) {
console.error("User profile load failed, default profile loaded", response);
- exports.profile = (new UserProfileModel(null, constants.defaultProfile));
+ exports.profile = (new UserProfileModel(null));
}
).finally(function () {
$rootScope.$broadcast(UserProfileEvents.loaded, exports);
@@ -85,7 +84,21 @@ angular
.in("id", userIds)
.project({"include": ["id", "userName"]});
});
- return $http.post(url, query.toJSON());
+ return $http.post(url, query.toJSONString());
+ };
+
+ exports.getUserForMetadataTile = function (userId) {
+ const url = paths.api.routes.user.filterAbsolute;
+ // Note: "imageUrls" are included no matter what as of
+ // v0.18.0 of baw-server.
+ var query = QueryBuilder.create(function (q) {
+ return q
+ .eq("id", userId)
+ .project({include: ["id", "userName"]});
+ });
+
+ return $http.post(url, query)
+ .then(x => UserProfileModel.makeFromApi(x));
};
diff --git a/src/components/services/vendorServices/externals.js b/src/components/services/vendorServices/externals.js
index c005ff60..111a0bf9 100644
--- a/src/components/services/vendorServices/externals.js
+++ b/src/components/services/vendorServices/externals.js
@@ -1,13 +1,13 @@
angular
.module("bawApp.vendorServices", [
- "bawApp.vendorServices.auto"
+ "bawApp.vendorServices.auto",
//"bawApp.services.core.mySillyLibrary"
])
.config(["humanize-durationProvider", "momentProvider",
- "$windowProvider", "d3Provider",
- function (humanizeDurationProvider, momentProvider, $windowProvider, d3Provider) {
+ "$windowProvider", "d3Provider", "c3Provider",
+ function (humanizeDurationProvider, momentProvider, $windowProvider, d3Provider, c3Provider) {
// HACK: add real duration formatting onto moment object!
var moment = momentProvider.configureVendorInstance();
@@ -144,4 +144,16 @@ angular
return this.attr("clip-path");
}
};
+
+ // augment c3
+ var c3 = c3Provider.configureVendorInstance();
+ let originalC3Generate = c3.generate;
+ c3.generate = function(...args) {
+ window.d3 = d3;
+
+ let result = originalC3Generate.apply(c3.chart.internal, args);
+
+ delete window.d3;
+ return result;
+ };
}]);
diff --git a/src/index.html b/src/index.html
index 8ec9b6d4..f27a67ed 100644
--- a/src/index.html
+++ b/src/index.html
@@ -16,7 +16,7 @@
+ src="https://maps.googleapis.com/maps/api/js?key=<%= build_configs.values.keys.googleMaps %>">
<% scripts.forEach( function ( file ) { %>
@@ -36,8 +36,8 @@
- {{::brand.name}}
+ ng-href="{{:: paths.api.links.homeAbsolute }}" target="_self">
+ {{:: brand.name }}
@@ -46,14 +46,14 @@
-
- Projects
-
- Listen
+ Listen
-
- Library
+ Library
-
@@ -83,78 +83,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -198,7 +142,7 @@
ga('create', '<%= build_configs.current.ga.trackingId %>', 'auto');
<% if (build_configs.current.key === "development") { %>
-
+
<% } %>