Martin Betak has uploaded a new change for review.

Change subject: oVirt.js: Initial rewiev commit [WIP] Don't merge!
......................................................................

oVirt.js: Initial rewiev commit [WIP] Don't merge!

Posting initial oVirt.js prototype by Vojtech, for easier review.

Change-Id: Ic6af0fe3089d70816dc3e138fed66163af5ecb28
Signed-off-by: Martin Betak <mbe...@redhat.com>
---
A oVirt.js
1 file changed, 360 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/10/32310/1

diff --git a/oVirt.js b/oVirt.js
new file mode 100644
index 0000000..7e84b07
--- /dev/null
+++ b/oVirt.js
@@ -0,0 +1,360 @@
+// oVirt.js prototype.
+// Requires ECMAScript 6 compliant environment:
+//  http://www.ecma-international.org/ecma-262/5.1/
+// Currently requires Lo-Dash library:
+//  http://lodash.com/
+
+// Contain the SDK within 'ovirt' global variable.
+var ovirt = {};
+
+// Expose services used by API implementation.
+// Each service should be an object containing reusable methods.
+// Services declared in this namespace have multiple roles:
+//  1. existing services can be overridden to achieve SDK portability for 
given runtime environment
+//  2. existing services can be used and new services can be added by SDK 
extensions or clients
+ovirt.svc = (function () {
+    'use strict';
+
+    var services = {};
+
+    // HTTP service for sending requests to remote server via HTTP protocol.
+    // This implementation uses XMLHttpRequest object supported by web 
browsers.
+    services.http = {
+        // Send HTTP request to remote server.
+        // 'httpParams' should be an object containing HTTP request parameters:
+        //  - 'method': HTTP method to use, defaults to 'GET'
+        //  - 'url': URL to send request to, defaults to empty string 
(document base URI)
+        //  - 'headers': object containing extra HTTP headers, defaults to 
empty object
+        //  - 'body': any value supported by XMLHttpRequest.send function, 
defaults to null
+        // 'onSuccess' should be a function to invoke when HTTP request 
completed successfully (status code 200).
+        // 'onError' should be a function to invoke when HTTP request failed 
(status code other than 200).
+        send(httpParams, onSuccess, onError) {
+            var xhr = new XMLHttpRequest();
+
+            // Sanitize inputs.
+            httpParams = httpParams || {};
+
+            xhr.onreadystatechange = function () {
+                if (xhr.readyState === 4) {
+                    var xhrSuccess = xhr.status >= 200 && xhr.status < 300;
+
+                    // Invoke appropriate handler function.
+                    if (xhrSuccess && _.isFunction(onSuccess)) {
+                        onSuccess(xhr.responseText);
+                    } else if (!xhrSuccess && _.isFunction(onError)) {
+                        onError(xhr.status);
+                    }
+                }
+            };
+
+            // Resolve HTTP request parameters.
+            var method = httpParams.method || 'GET';
+            var url = httpParams.url || '';
+            var headers = httpParams.headers || {};
+            var body = httpParams.body || null;
+
+            // Initiate new request.
+            xhr.open(method, url, true);
+
+            // Include credentials such as HTTP auth information and cookies 
in cross-site requests.
+            xhr.withCredentials = true;
+
+            // Apply request headers.
+            Object.keys(headers).forEach(name => {
+                var value = headers[name] && headers[name].toString();
+                xhr.setRequestHeader(name, value || '');
+            });
+
+            // Fire off the request.
+            xhr.send(body);
+        }
+    };
+
+    return services;
+})();
+
+ovirt.api = (function (services, util) {
+    'use strict';
+
+    var api = {};
+
+    class Operation {
+        constructor (spec) {
+            this.spec = spec;
+            this.transformResult = _getFnOrIdentity(spec.transformResult);
+            this.onSuccess = _.noop;
+            this.onError = _.noop;
+        }
+
+        static get (spec) {
+            spec.httpMethod = 'GET';
+            return new Operation(spec);
+        }
+
+        static put (spec) {
+            spec.httpMethod = 'PUT';
+            return new Operation(spec);
+        }
+
+        static post (spec) {
+            spec.httpMethod = 'POST';
+            return new Operation(spec);
+        }
+
+        static delete (spec) {
+            spec.httpMethod = 'DELETE';
+            return new Operation(spec);
+        }
+
+        success(callback) {
+            this.onSuccess = _getFnOrNoop(callback);
+            return this;
+        }
+
+        error() {
+            this.onError = _getFnOrNoop(callback);
+            return this;
+        }
+
+        run() {
+            // Prepare request headers.
+            var headers = this.spec.httpHeaders || {};
+            headers['Accept'] = 'application/json';
+            headers['Content-Type'] = 'application/json';
+            headers['Filter'] = api.options.filterResults;
+
+            // Prepare request parameters.
+            var httpParams = {
+                method: this.spec.httpMethod,
+                url: api.options.engineBaseUrl + spec.apiContextPath,
+                headers,
+                body: spec.httpBody && JSON.stringify(spec.httpBody)
+            };
+
+            services.http.send(httpParams,
+                responseText => {
+                    try {
+                        // Parse response as JSON.
+                        var result = JSON.parse(responseText);
+                        // Transform resulting object, if necessary,
+                        // and Invoke operation success callback.
+                        this.onSuccess(this.transformResult(result));
+                    } catch (e) {
+                        // TODO handle error properly
+                        console.log('Error while parsing response: ' + e);
+                    }
+                },
+                errorCode => {
+                    // TODO handle error properly
+                    console.log('Request failed with status code ' + 
errorCode);
+                });
+        }
+
+        // If 'fn' is a function, return that function.
+        // Otherwise, return a no-op function.
+        static _getFnOrNoop (fn) {
+            return _.isFunction(fn) ? fn : _.noop;
+        }
+
+        // If 'fn' is a function, return that function.
+        // Otherwise, return and identity function.
+        static _getFnOrIdentity (fn) {
+            return _.isFunction(fn) ? fn : _.identity;
+        }
+    }
+
+    class Resource {
+        constructor(data) {
+            this.apiContextPath = data && data.href;
+            this._assignData(data);
+
+            // Augment resource with nested resource collections.
+            var subCollections = data.subCollections || {};
+            Object.keys(subCollections).forEach(name => {
+                this[name] = 
ResourceCollection.withContextPath(subCollections[name]);
+            });
+
+            // Augment resource with supported actions.
+            var actions = data.actions || {};
+            Object.keys(actions).forEach(name => {
+                // Return operation to invoke given action using 'actionData' 
on this resource.
+                this[name] = function (actionData) {
+                    return Operation.post({
+                        httpBody: actionData,
+                        apiContextPath: actions[name]
+                    });
+                };
+            });
+        }
+
+        static fromData (data) {
+            return new Resource(_transformResourceData(data));
+        }
+
+        update () {
+            return Operation.put({
+                apiContextPath: this.apiContextPath,
+                httpBody: _computeDiff(this.originalData, this.data),
+                transformResult: data => {
+                    this._assignData(data);
+                    return data;
+                }
+            });
+        }
+
+        delete () {
+            return Operation.delete({
+                apiContextPath: this.apiContextPath
+            });
+        }
+
+        _assignData (resourceData) {
+            var data = resourceData || {};
+            this.originalData = _.cloneDeep(data);
+
+            // Access resource data via getter/setter function with 
merge-on-set behavior.
+            // TODO prevent setting new properties (ones not already present 
in full data representation)
+            that.data = _makeGetSetMergeFn(that, data);
+        }
+
+        // Transform raw resource data for use by resource objects.
+        static _transformResourceData (data) {
+            function definePropertyReadOnly (propName, propValue) {
+                Object.defineProperty(data, propName, {
+                    value: propValue,
+                    writable: false,
+                    configurable: true,
+                    enumerable: true
+                });
+            }
+
+            function reduceLinks (array) {
+                return array.reduce((result, link) => {
+                    result[link.rel] = link.href;
+                    return result;
+                }, {});
+            }
+
+            // Make 'id' non-writable.
+            definePropertyReadOnly('id', data.id);
+
+            // Make 'href' non-writable.
+            definePropertyReadOnly('href', data.href);
+
+            // Transform and expose nested resource collections.
+            var subCollectionLinks = data.link || [];
+            definePropertyReadOnly('subCollections', 
reduceLinks(subCollectionLinks));
+            delete data.link;
+
+            // Transform and expose supported actions.
+            var actionLinks = (data.actions && data.actions.link) || [];
+            definePropertyReadOnly('actions', reduceLinks(actionLinks));
+
+            return data;
+        }
+
+        // Compute change between two objects and return a diff object 
containing the difference.
+        // The diff object will be null if there is no change.
+        static _computeDiff (original, modified) {
+            var diff = {};
+
+            if (_.isObject(original) && _.isObject(modified)) {
+                Object.keys(modified).forEach(function (key) {
+                    var propertyDiff;
+
+                    if (!original.hasOwnProperty(key)) {
+                        // Add new property missing in 'original' 
automatically into diff object.
+                        diff[key] = modified[key];
+                    } else {
+                        // Otherwise, compute change for existing property 
recursively and update diff object.
+                        propertyDiff = computeDiff(original[key], 
modified[key]);
+                        if (propertyDiff) {
+                            diff[key] = propertyDiff;
+                        }
+                    }
+                });
+                return _.isEqual(diff, {}) ? null : diff;
+            } else {
+                return _.isEqual(original, modified) ? null : modified;
+            }
+        }
+
+        // Make a function acting as getter and setter on object value with 
merge-on-set behavior.
+        // Calling resulting function with an object argument will merge 
properties of that object into 'target' object and return 'that'.
+        // Otherwise, 'target' object will be returned.
+        static _makeGetSetMergeFn(that, target) {
+            return function (update) {
+                if (_.isObject(update)) {
+                    _.merge(target, update);
+                    return that;
+                } else {
+                    return target;
+                }
+            };
+        }
+    }
+
+    class ResourceCollection {
+        constructor (apiContextPath) {
+            this.apiContextPath = apiContextPath;
+        }
+
+        static withContextPath (apiContextPath) {
+            return new ResourceCollection(apiContextPath);
+        }
+
+        get (id) {
+            return Operation.get({
+                apiContextPath: `${this.apiContextPath}/${id}`,
+                transformResult: Resource.fromData
+            });
+        }
+
+        list () {
+            return Operation.get({
+                apiContextPath: this.apiContextPath,
+                transformResult: _makeResourceArray
+            });
+        }
+
+        add (data) {
+            return Operation.post({
+                apiContextPath: this.apiContextPath,
+                httpBody: data,
+                transformResult: Resource.fromData
+            });
+        }
+
+        delete (id) {
+            return Operation.delete({
+                apiContextPath: `${this.apiContextPath}/${id}`
+            })
+        }
+
+        // Extract raw resource data from 'obj' and return array of resource 
objects.
+        static _makeResourceArray (obj) {
+            // Extract resource data, 'obj' should contain a single enumerable 
property.
+            var keys = Object.keys(obj);
+            var dataArray = (keys.length === 1) ? obj[keys[0]] : [];
+
+            // Sanitize resource data.
+            dataArray = Array.isArray(dataArray) ? dataArray : [];
+
+            // Return resource objects.
+            return dataArray.map(Resource.fromData);
+        }
+    }
+
+    api = {
+        options: {
+            // Engine base URL, without a trailing slash.
+            engineBaseUrl: 'http://127.0.0.1:8080',
+            // Flag indicating whether to filter results based on user's 
permissions.
+            filterResults: false
+        }
+    };
+
+    api.datacenters = 
ResourceCollection.withContextPath('/ovirt-engine/api/datacenters');
+
+    return api;
+})(ovirt.services);
\ No newline at end of file


-- 
To view, visit http://gerrit.ovirt.org/32310
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic6af0fe3089d70816dc3e138fed66163af5ecb28
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Martin Betak <mbe...@redhat.com>
_______________________________________________
Engine-patches mailing list
Engine-patches@ovirt.org
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to