From: Javier García Date: Mon, 1 Dec 2014 12:03:09 +0000 (+0100) Subject: Merge branch 'fed4fire' into onelab X-Git-Tag: myslice-1.1~27 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=3aae671a1ca0cef870a268b88ddcde8d520d7621;hp=de6f0bbb7cd3ae3086a6485072803b53f2c4d9f6;p=myslice.git Merge branch 'fed4fire' into onelab Conflicts: plugins/queryupdater/static/js/queryupdater.js portal/templates/base.html --- diff --git a/README.manifold-tables b/README.manifold-tables new file mode 100644 index 00000000..6852dee0 --- /dev/null +++ b/README.manifold-tables @@ -0,0 +1 @@ +manifold-tables -A -o authority -f \* -a R -j CACHE diff --git a/manifoldapi/manifoldapi.py b/manifoldapi/manifoldapi.py index b105bf30..a8b2ab9a 100644 --- a/manifoldapi/manifoldapi.py +++ b/manifoldapi/manifoldapi.py @@ -4,6 +4,7 @@ import copy, xmlrpclib from myslice.configengine import ConfigEngine from django.contrib import messages +from django.shortcuts import redirect from manifoldresult import ManifoldResult, ManifoldCode, ManifoldException from manifold.core.result_value import ResultValue @@ -154,7 +155,7 @@ def execute_query(request, query): request.session.flush() #raise Exception, "User not authenticated" host = request.get_host() - return redirect(host) + return redirect('/') manifold_api_session_auth = request.session['manifold']['auth'] return _execute_query(request, query, manifold_api_session_auth) diff --git a/manifoldapi/manifoldproxy.py b/manifoldapi/manifoldproxy.py index c16ffdee..99959aee 100644 --- a/manifoldapi/manifoldproxy.py +++ b/manifoldapi/manifoldproxy.py @@ -40,7 +40,7 @@ with the query passed using POST""" # format_out: how to serve the results if format != 'json': print "manifoldproxy.proxy: unexpected format %s -- exiting"%format - return + return HttpResponse ({"ret":0}, mimetype="application/json") try: # translate incoming POST request into a query object if debug: print 'manifoldproxy.proxy: request.POST',request.POST @@ -84,6 +84,7 @@ with the query passed using POST""" print "** PROXY ERROR **",e import traceback traceback.print_exc() + return HttpResponse ({"ret":0}, mimetype="application/json") #################### # see CSRF_FAILURE_VIEW in settings.py diff --git a/manifoldapi/static/js/hashtable.js b/manifoldapi/static/js/hashtable.js new file mode 100644 index 00000000..3922671f --- /dev/null +++ b/manifoldapi/static/js/hashtable.js @@ -0,0 +1,402 @@ +/** + * @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called + * Hashtable in the global scope. + * + * http://www.timdown.co.uk/jshashtable/ + * Copyright %%build:year%% Tim Down. + * Version: %%build:version%% + * Build date: %%build:date%% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var Hashtable = (function(UNDEFINED) { + var FUNCTION = "function", STRING = "string", UNDEF = "undefined"; + + // Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not + // having these (e.g. IE <= 5), we bail out now and leave Hashtable null. + if (typeof encodeURIComponent == UNDEF || + Array.prototype.splice === UNDEFINED || + Object.prototype.hasOwnProperty === UNDEFINED) { + return null; + } + + function toStr(obj) { + return (typeof obj == STRING) ? obj : "" + obj; + } + + function hashObject(obj) { + var hashCode; + if (typeof obj == STRING) { + return obj; + } else if (typeof obj.hashCode == FUNCTION) { + // Check the hashCode method really has returned a string + hashCode = obj.hashCode(); + return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode); + } else { + return toStr(obj); + } + } + + function merge(o1, o2) { + for (var i in o2) { + if (o2.hasOwnProperty(i)) { + o1[i] = o2[i]; + } + } + } + + function equals_fixedValueHasEquals(fixedValue, variableValue) { + return fixedValue.equals(variableValue); + } + + function equals_fixedValueNoEquals(fixedValue, variableValue) { + return (typeof variableValue.equals == FUNCTION) ? + variableValue.equals(fixedValue) : (fixedValue === variableValue); + } + + function createKeyValCheck(kvStr) { + return function(kv) { + if (kv === null) { + throw new Error("null is not a valid " + kvStr); + } else if (kv === UNDEFINED) { + throw new Error(kvStr + " must not be undefined"); + } + }; + } + + var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value"); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Bucket(hash, firstKey, firstValue, equalityFunction) { + this[0] = hash; + this.entries = []; + this.addEntry(firstKey, firstValue); + + if (equalityFunction !== null) { + this.getEqualityFunction = function() { + return equalityFunction; + }; + } + } + + var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2; + + function createBucketSearcher(mode) { + return function(key) { + var i = this.entries.length, entry, equals = this.getEqualityFunction(key); + while (i--) { + entry = this.entries[i]; + if ( equals(key, entry[0]) ) { + switch (mode) { + case EXISTENCE: + return true; + case ENTRY: + return entry; + case ENTRY_INDEX_AND_VALUE: + return [ i, entry[1] ]; + } + } + } + return false; + }; + } + + function createBucketLister(entryProperty) { + return function(aggregatedArr) { + var startIndex = aggregatedArr.length; + for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { + aggregatedArr[startIndex + i] = entries[i][entryProperty]; + } + }; + } + + Bucket.prototype = { + getEqualityFunction: function(searchValue) { + return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; + }, + + getEntryForKey: createBucketSearcher(ENTRY), + + getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE), + + removeEntryForKey: function(key) { + var result = this.getEntryAndIndexForKey(key); + if (result) { + this.entries.splice(result[0], 1); + return result[1]; + } + return null; + }, + + addEntry: function(key, value) { + this.entries.push( [key, value] ); + }, + + keys: createBucketLister(0), + + values: createBucketLister(1), + + getEntries: function(destEntries) { + var startIndex = destEntries.length; + for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { + // Clone the entry stored in the bucket before adding to array + destEntries[startIndex + i] = entries[i].slice(0); + } + }, + + containsKey: createBucketSearcher(EXISTENCE), + + containsValue: function(value) { + var entries = this.entries, i = entries.length; + while (i--) { + if ( value === entries[i][1] ) { + return true; + } + } + return false; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Supporting functions for searching hashtable buckets + + function searchBuckets(buckets, hash) { + var i = buckets.length, bucket; + while (i--) { + bucket = buckets[i]; + if (hash === bucket[0]) { + return i; + } + } + return null; + } + + function getBucketForHash(bucketsByHash, hash) { + var bucket = bucketsByHash[hash]; + + // Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype + return ( bucket && (bucket instanceof Bucket) ) ? bucket : null; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Hashtable() { + var buckets = []; + var bucketsByHash = {}; + var properties = { + replaceDuplicateKey: true, + hashCode: hashObject, + equals: null + }; + + var arg0 = arguments[0], arg1 = arguments[1]; + if (arg1 !== UNDEFINED) { + properties.hashCode = arg0; + properties.equals = arg1; + } else if (arg0 !== UNDEFINED) { + merge(properties, arg0); + } + + var hashCode = properties.hashCode, equals = properties.equals; + + this.properties = properties; + + this.put = function(key, value) { + checkKey(key); + checkValue(value); + var hash = hashCode(key), bucket, bucketEntry, oldValue = null; + + // Check if a bucket exists for the bucket key + bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it already contains this key + bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so replace the old value. + // Also, we optionally replace the key so that the latest key is stored. + if (properties.replaceDuplicateKey) { + bucketEntry[0] = key; + } + oldValue = bucketEntry[1]; + bucketEntry[1] = value; + } else { + // The bucket does not contain an entry for this key, so add one + bucket.addEntry(key, value); + } + } else { + // No bucket exists for the key, so create one and put our key/value mapping in + bucket = new Bucket(hash, key, value, equals); + buckets.push(bucket); + bucketsByHash[hash] = bucket; + } + return oldValue; + }; + + this.get = function(key) { + checkKey(key); + + var hash = hashCode(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it contains this key + var bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so return the value. + return bucketEntry[1]; + } + } + return null; + }; + + this.containsKey = function(key) { + checkKey(key); + var bucketKey = hashCode(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, bucketKey); + + return bucket ? bucket.containsKey(key) : false; + }; + + this.containsValue = function(value) { + checkValue(value); + var i = buckets.length; + while (i--) { + if (buckets[i].containsValue(value)) { + return true; + } + } + return false; + }; + + this.clear = function() { + buckets.length = 0; + bucketsByHash = {}; + }; + + this.isEmpty = function() { + return !buckets.length; + }; + + var createBucketAggregator = function(bucketFuncName) { + return function() { + var aggregated = [], i = buckets.length; + while (i--) { + buckets[i][bucketFuncName](aggregated); + } + return aggregated; + }; + }; + + this.keys = createBucketAggregator("keys"); + this.values = createBucketAggregator("values"); + this.entries = createBucketAggregator("getEntries"); + + this.remove = function(key) { + checkKey(key); + + var hash = hashCode(key), bucketIndex, oldValue = null; + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + + if (bucket) { + // Remove entry from this bucket for this key + oldValue = bucket.removeEntryForKey(key); + if (oldValue !== null) { + // Entry was removed, so check if bucket is empty + if (bucket.entries.length == 0) { + // Bucket is empty, so remove it from the bucket collections + bucketIndex = searchBuckets(buckets, hash); + buckets.splice(bucketIndex, 1); + delete bucketsByHash[hash]; + } + } + } + return oldValue; + }; + + this.size = function() { + var total = 0, i = buckets.length; + while (i--) { + total += buckets[i].entries.length; + } + return total; + }; + } + + Hashtable.prototype = { + each: function(callback) { + var entries = this.entries(), i = entries.length, entry; + while (i--) { + entry = entries[i]; + callback(entry[0], entry[1]); + } + }, + + equals: function(hashtable) { + var keys, key, val, count = this.size(); + if (count == hashtable.size()) { + keys = this.keys(); + while (count--) { + key = keys[count]; + val = hashtable.get(key); + if (val === null || val !== this.get(key)) { + return false; + } + } + return true; + } + return false; + }, + + putAll: function(hashtable, conflictCallback) { + var entries = hashtable.entries(); + var entry, key, value, thisValue, i = entries.length; + var hasConflictCallback = (typeof conflictCallback == FUNCTION); + while (i--) { + entry = entries[i]; + key = entry[0]; + value = entry[1]; + + // Check for a conflict. The default behaviour is to overwrite the value for an existing key + if ( hasConflictCallback && (thisValue = this.get(key)) ) { + value = conflictCallback(key, thisValue, value); + } + this.put(key, value); + } + }, + + clone: function() { + var clone = new Hashtable(this.properties); + clone.putAll(this); + return clone; + } + }; + + Hashtable.prototype.toQueryString = function() { + var entries = this.entries(), i = entries.length, entry; + var parts = []; + while (i--) { + entry = entries[i]; + parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) ); + } + return parts.join("&"); + }; + + return Hashtable; +})(); diff --git a/manifoldapi/static/js/manifold-query.js b/manifoldapi/static/js/manifold-query.js index 3116f84d..64eb3a7a 100644 --- a/manifoldapi/static/js/manifold-query.js +++ b/manifoldapi/static/js/manifold-query.js @@ -1,3 +1,15 @@ +var guid = (function() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return function() { + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + }; +})(); + function ManifoldQuery(action, object, timestamp, filters, params, fields, unique, query_uuid, aq, sq) { // get, update, delete, create var action; @@ -131,7 +143,7 @@ INSERT INTO object VALUES(field=value) }); }; - if (this.analyzed_query !== undefined) + if (!!this.analyzed_query) query = this.analyzed_query; else query = this; @@ -257,7 +269,10 @@ INSERT INTO object VALUES(field=value) else this.unique = unique; - this.query_uuid = query_uuid; + if (typeof unique == "undefined") + this.query_uuid = guid(); + else + this.query_uuid = query_uuid; if (typeof aq == "undefined") this.analyzed_query = null; diff --git a/manifoldapi/static/js/manifold.js b/manifoldapi/static/js/manifold.js index fa5415a8..e261efeb 100644 --- a/manifoldapi/static/js/manifold.js +++ b/manifoldapi/static/js/manifold.js @@ -38,27 +38,60 @@ var FIELD_REMOVED = 5; var CLEAR_FIELDS = 6; var NEW_RECORD = 7; var CLEAR_RECORDS = 8; + +/** + * event: FIELD_STATE_CHANGED + * + * Parameters: + * dict : + * .state : ???? used to be FIELD_REQUEST_ADD / FIELD_REQUEST_REMOVE + * .key : ??? the key fields of the record + * .op : the key of the record who has received an update + * .value : the new state of the record + */ var FIELD_STATE_CHANGED = 9; var IN_PROGRESS = 101; -var DONE = 102; +var DONE = 102; //XXX Should be harmonized with query state /* Update requests related to subqueries */ + +/* var SET_ADD = 201; var SET_REMOVED = 202; +*/ // request +/* var FIELD_REQUEST_CHANGE = 301; var FIELD_REQUEST_ADD = 302; var FIELD_REQUEST_REMOVE = 303; var FIELD_REQUEST_ADD_RESET = 304; var FIELD_REQUEST_REMOVE_RESET = 305; -// status +*/ +// status (XXX Should be deprecated) var FIELD_REQUEST_PENDING = 401; var FIELD_REQUEST_SUCCESS = 402; var FIELD_REQUEST_FAILURE = 403; +var STATUS_OKAY = 404; +var STATUS_SET_WARNING = 405; +var STATUS_ADD_WARNING = 406; +var STATUS_REMOVE_WARNING = 407; +var STATUS_RESET = 408; + +/* Requests for query cycle */ +var RUN_UPDATE = 601; + +/* MANIFOLD types */ +var TYPE_VALUE = 1; +var TYPE_RECORD = 2; +var TYPE_LIST_OF_VALUES = 3; +var TYPE_LIST_OF_RECORDS = 4; + +/****************************************************************************** + * QUERY STATUS (for manifold events) + ******************************************************************************/ -/* Query status */ var STATUS_NONE = 500; // Query has not been started yet var STATUS_GET_IN_PROGRESS = 501; // Query has been sent, no result has been received var STATUS_GET_RECEIVED = 502; // Success @@ -68,19 +101,59 @@ var STATUS_UPDATE_IN_PROGRESS = 505; var STATUS_UPDATE_RECEIVED = 506; var STATUS_UPDATE_ERROR = 507; -/* Requests for query cycle */ -var RUN_UPDATE = 601; +/****************************************************************************** + * QUERY STATE (for query_store) + ******************************************************************************/ -/* MANIFOLD types */ -var TYPE_VALUE = 1; -var TYPE_RECORD = 2; -var TYPE_LIST_OF_VALUES = 3; -var TYPE_LIST_OF_RECORDS = 4; +// XXX Rendundant with query status ? + +var QUERY_STATE_INIT = 0; +var QUERY_STATE_INPROGRESS = 1; +var QUERY_STATE_DONE = 2; + +/****************************************************************************** + * RECORD STATES (for query_store) + ******************************************************************************/ +var STATE_SET = 0; +var STATE_VALUE = 1; +var STATE_WARNINGS = 2; +var STATE_VISIBLE = 3; + +// ACTIONS +var STATE_SET_CHANGE = 0; +var STATE_SET_ADD = 1; +var STATE_SET_REMOVE = 2; +var STATE_SET_CLEAR = 3; + +// STATE_SET : enum +var STATE_SET_IN = 0; +var STATE_SET_OUT = 1; +var STATE_SET_IN_PENDING = 2; +var STATE_SET_OUT_PENDING = 3; +var STATE_SET_IN_SUCCESS = 4; +var STATE_SET_OUT_SUCCESS = 5; +var STATE_SET_IN_FAILURE = 6; +var STATE_SET_OUT_FAILURE = 7; +var STATE_VALUE_CHANGE_PENDING = 8; +var STATE_VALUE_CHANGE_SUCCESS = 9; +var STATE_VALUE_CHANGE_FAILURE = 10; + +// STATE_WARNINGS : dict + +// STATE_VISIBLE : boolean + +/****************************************************************************** + * CONSTRAINTS + ******************************************************************************/ + +var CONSTRAINT_RESERVABLE_LEASE = 0; + +var CONSTRAINT_RESERVABLE_LEASE_MSG = "Configuration required: this resource needs to be scheduled"; // A structure for storing queries -function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled) { +function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled, domain_query_ext) { /* Constructor */ if (typeof query == "undefined") @@ -90,10 +163,36 @@ function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, dis this.main_query_ext = (typeof main_query_ext == "undefined") ? null : main_query_ext; this.update_query_ext = (typeof update_query_ext == "undefined") ? null : update_query_ext; this.update_query_orig_ext = (typeof update_query_orig_ext == "undefined") ? null : update_query_orig_ext; - this.disabled = (typeof update_query_ext == "undefined") ? false : disabled; + this.disabled = (typeof disabled == "undefined") ? false : disabled; + + // A domain query is a query that is issued to retrieve all possible values for a set + // eg. all resources that can be attached to a slice + // It is null unless we are a subquery for which a domain query has been issued + this.domain_query_ext = (typeof domain_query_ext == "undefined") ? null : domain_query_ext; + + // Set members to buffer until the domain query is completed + // A list of keys + this.set_members = []; + + // The set query is the query for which the domain query has been issued. + // It is null unless the query is a domain query + this.set_query_ext = (typeof set_query_ext == "undefined") ? null : domain_query_ext; - this.status = null; - this.results = null; + this.query_state = QUERY_STATE_INIT; + + // Results from a query consists in a dict that maps keys to records + this.records = new Hashtable(); + + // Status is a dict that maps keys to record status + this.state = new Hashtable(); + + // Filters that impact visibility in the local interface + this.filters = []; + + // XXX Until we find a better solution + this.num_pending = 0; + this.num_unconfigured = 0; + // update_query null unless we are a main_query (aka parent_query == null); only main_query_fields can be updated... } @@ -142,13 +241,37 @@ function QueryStore() { // We also need to insert all queries and subqueries from the analyzed_query // XXX We need the root of all subqueries query.iter_subqueries(function(sq, data, parent_query) { - if (parent_query) + var parent_query_ext; + if (parent_query) { parent_query_ext = manifold.query_store.find_analyzed_query_ext(parent_query.query_uuid); - else + } else { parent_query_ext = null; + } // XXX parent_query_ext == false // XXX main.subqueries = {} # Normal, we need analyzed_query sq_ext = new QueryExt(sq, parent_query_ext, query_ext) + + if (parent_query) { + /* Let's issue a query for the subquery domain. This query will not need any update etc. + eg. for resources in a slice, we also query all resources */ + var all_fields = manifold.metadata.get_field_names(sq.object); + var domain_query = new ManifoldQuery('get', sq.object, 'now', [], {}, all_fields); + //var domain_query = new ManifoldQuery('get', sq.object); + + console.log("Created domain query", domain_query); + var domain_query_ext = new QueryExt(domain_query); + + domain_query_ext.set_query_ext = sq_ext; + sq_ext.domain_query_ext = domain_query_ext; + + // One of these two is useless ? + manifold.query_store.main_queries[domain_query.query_uuid] = domain_query_ext; + manifold.query_store.analyzed_queries[domain_query.query_uuid] = domain_query_ext; + + // XXX This query is run before the plugins are initialized and listening + manifold.run_query(domain_query); + } + manifold.query_store.analyzed_queries[sq.query_uuid] = sq_ext; }); @@ -157,21 +280,343 @@ function QueryStore() { /* Searching */ - this.find_query_ext = function(query_uuid) { + this.find_query_ext = function(query_uuid) + { return this.main_queries[query_uuid]; } - this.find_query = function(query_uuid) { + this.find_query = function(query_uuid) + { return this.find_query_ext(query_uuid).query; } - this.find_analyzed_query_ext = function(query_uuid) { + this.find_analyzed_query_ext = function(query_uuid) + { return this.analyzed_queries[query_uuid]; } - this.find_analyzed_query = function(query_uuid) { + this.find_analyzed_query = function(query_uuid) + { return this.find_analyzed_query_ext(query_uuid).query; } + + this.state_dict_create = function(default_set) + { + default_set = (default_set === undefined) ? STATE_SET_OUT : default_set; + var state_dict = {}; + // We cannot use constants in literal definition, so... + state_dict[STATE_WARNINGS] = {}; + state_dict[STATE_SET] = default_set; + state_dict[STATE_VISIBLE] = true; + return state_dict; + } + + // RECORDS + + this.set_records = function(query_uuid, records, default_set) + { + default_set = (default_set === undefined) ? STATE_SET_OUT : default_set; + + var self = this; + var query_ext = this.find_analyzed_query_ext(query_uuid); + var record_key = manifold.metadata.get_key(query_ext.query.object); + $.each(records, function(i, record) { + var key = manifold.metadata.get_key(query_ext.query.object); + // ["start_time", "resource", "end_time"] + // ["urn"] + + var record_key_value = manifold.record_get_value(record, record_key); + query_ext.records.put(record_key_value, record); + + if (!(query_ext.state.get(record_key_value))) + query_ext.state.put(record_key_value, self.state_dict_create(default_set)); + }); + } + + this.get_records = function(query_uuid) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + return query_ext.records.values(); + } + + this.get_record = function(query_uuid, record_key) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + return query_ext.records.get(record_key); + } + + this.add_record = function(query_uuid, record, new_state) + { + var query_ext, key, record_key; + query_ext = this.find_analyzed_query_ext(query_uuid); + + if (typeof(record) == 'object') { + key = manifold.metadata.get_key(query_ext.query.object); + record_key = manifold.record_get_value(record, key); + } else { + record_key = record; + } + + var record_entry = query_ext.records.get(record_key); + if (!record_entry) + query_ext.records.put(record_key, record); + + manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state); + } + + this.remove_record = function(query_uuid, record, new_state) + { + var query_ext, key, record_key; + query_ext = this.find_analyzed_query_ext(query_uuid); + + if (typeof(record) == 'object') { + key = manifold.metadata.get_key(query_ext.query.object); + record_key = manifold.record_get_value(record, key); + } else { + record_key = record; + } + + manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state); + } + + this.iter_records = function(query_uuid, callback) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + query_ext.records.each(callback); + //callback = function(record_key, record) + } + + this.iter_visible_records = function(query_uuid, callback) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + query_ext.records.each(function(record_key, record) { + if (query_ext.state.get(record_key)[STATE_VISIBLE]) // .STATE_VISIBLE would be for the string key + callback(record_key, record); + }); + //callback = function(record_key, record) + + } + + // STATE + + this.set_record_state = function(query_uuid, result_key, state, value) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + var state_dict = query_ext.state.get(result_key); + if (!state_dict) + state_dict = this.state_dict_create(); + + state_dict[state] = value; + + query_ext.state.put(result_key, state_dict); + } + + this.get_record_state = function(query_uuid, result_key, state) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + var state_dict = query_ext.state.get(result_key); + if (!state_dict) + return null; + return state_dict[state]; + } + + // FILTERS + + this.add_filter = function(query_uuid, filter) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + // XXX When we update a filter + query_ext.filters.push(filter); + + this.apply_filters(query_uuid); + + } + + this.update_filter = function(query_uuid, filter) + { + // XXX + + this.apply_filters(query_uuid); + } + + this.remove_filter = function(query_uuid, filter) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + query_ext.filters = $.grep(query_ext.filters, function(x) { + return x == filter; + }); + + this.apply_filters(query_uuid); + } + + this.get_filters = function(query_uuid) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + return query_ext.filters; + } + + this.recount = function(query_uuid) + { + var query_ext; + var is_reserved, is_pending, in_set, is_unconfigured; + + query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid); + query_ext.num_pending = 0; + query_ext.num_unconfigured = 0; + + this.iter_records(query_uuid, function(record_key, record) { + var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET); + var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS); + + is_reserved = (record_state == STATE_SET_IN) + || (record_state == STATE_SET_OUT_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); + + is_pending = (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_OUT_PENDING); + + in_set = (record_state == STATE_SET_IN) // should not have warnings + || (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings + + is_unconfigured = (in_set && !$.isEmptyObject(record_warnings)); + + /* Let's update num_pending and num_unconfigured at this stage */ + if (is_pending) + query_ext.num_pending++; + if (is_unconfigured) + query_ext.num_unconfigured++; + }); + + } + + this.apply_filters = function(query_uuid) + { + var start = new Date().getTime(); + + // Toggle visibility of records according to the different filters. + + var self = this; + var filters = this.get_filters(query_uuid); + var col_value; + /* Let's update num_pending and num_unconfigured at this stage */ + + // Adapted from querytable._querytable_filter() + + this.iter_records(query_uuid, function(record_key, record) { + var is_reserved, is_pending, in_set, is_unconfigured; + var visible = true; + + var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET); + var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS); + + is_reserved = (record_state == STATE_SET_IN) + || (record_state == STATE_SET_OUT_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); + + is_pending = (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_OUT_PENDING); + + in_set = (record_state == STATE_SET_IN) // should not have warnings + || (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings + + is_unconfigured = (in_set && !$.isEmptyObject(record_warnings)); + + // We go through each filter and decide whether it affects the visibility of the record + $.each(filters, function(index, filter) { + var key = filter[0]; + var op = filter[1]; + var value = filter[2]; + + + /* We do some special handling for the manifold:status filter + * predicates. */ + + if (key == 'manifold:status') { + if (op != '=' && op != '==') { + // Unsupported filter, let's ignore it + console.log("Unsupported filter on manifold:status. Should be EQUAL only."); + return true; // ~ continue + } + + switch (value) { + case 'reserved': + // true => ~ continue + // false => ~ break + visible = is_reserved; + return visible; + case 'unconfigured': + visible = is_unconfigured; + return visible; + case 'pending': + visible = is_pending; + return visible; + } + return false; // ~ break + } + + /* Normal filtering behaviour (according to the record content) follows... */ + col_value = manifold.record_get_value(record, key); + + // When the filter does not match, we hide the column by default + if (col_value === 'undefined') { + visible = false; + return false; // ~ break + } + + // XXX This should accept pluggable filtering functions. + + + /* Test whether current filter is compatible with the column */ + if (op == '=' || op == '==') { + if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + }else if (op == 'included') { + $.each(value, function(i,x) { + if(x == col_value){ + visible = true; + return false; // ~ break + }else{ + visible = false; + } + }); + }else if (op == '!=') { + if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='<') { + if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='>') { + if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='<=' || op=='≤') { + if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='>=' || op=='≥') { + if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + }else{ + // How to break out of a loop ? + alert("filter not supported"); + return false; // break + } + + }); + + // Set the visibility status in the query store + self.set_record_state(query_uuid, record_key, STATE_VISIBLE, visible); + }); + + var end = new Date().getTime(); + console.log("APPLY FILTERS took", end - start, "ms"); + + } + } /*! @@ -201,6 +646,124 @@ var manifold = { } }, + /** + * Args: + * fields: A String instance (field name), or a set of String instances + * (field names) # XXX tuple !! + * Returns: + * If fields is a String, return the corresponding value. + * If fields is a set, return a tuple of corresponding value. + * + * Raises: + * KeyError if at least one of the fields is not found + */ + record_get_value: function(record, fields) + { + if (typeof(fields) === 'string') { + if (fields.indexOf('.') != -1) { + key_subkey = key.split('.', 2); + key = key_subkey[0]; + subkey = key_subkey[1]; + + if (record.indexOf(key) == -1) { + return null; + } + // Tests if the following is an array (typeof would give object) + if (Object.prototype.toString.call(record[key]) === '[object Array]') { + // Records + return $.map(record[key], function(subrecord) { return manifold.record_get_value(subrecord, subkey) }); + } else if (typeof(record) == 'object') { + // Record + return manifold.record_get_value(record[key], subkey); + } else { + console.log('Unknown field'); + } + } else { + return record[fields]; + } + } else { + // see. get_map_entries + if (fields.length == 1) + return manifold.record_get_value(record, fields[0]) + + // Build a new record + var ret = {}; + $.each(fields, function(i, field) { + ret[field] = manifold.record_get_value(record, field); + }); + ret.hashCode = record.hashCode; + ret.equals = record.equals; + return ret; + // this was an array, we want a dictionary + //return $.map(fields, function(x) { manifold.record_get_value(record, x) }); + + } + }, + + record_hashcode: function(key_fields) + { + return function() { + ret = ""; + for (i=0; i < key_fields.length; i++) + ret += "@@" + this[key_fields[i]]; + return ret; + }; + }, + + _record_equals: function(self, other, key_fields) + { + for (i=0; i < key_fields.length; i++) { + var this_value = self[key_fields[i]]; + var other_value = other[key_fields[i]]; + + var this_type = manifold.get_type(this_value); + var other_type = manifold.get_type(other_value); + if (this_type != other_type) + return false; + + switch (this_type) { + case TYPE_VALUE: + case TYPE_LIST_OF_VALUES: + if (this_value != other_value) + return false; + break; + case TYPE_RECORD: + if (!(_record_equals(this_value, other_value, key_fields))) + return false; + break; + case TYPE_LIST_OF_RECORDS: + if (this_value.length != other_value.length) + return false; + for (i = 0; i < this_value.length; i++) + if (!(_record_equals(this_value, other_value, key_fields))) + return false; + break; + } + } + return true; + }, + + record_equals: function(key_fields) + { + return function(other) { + return manifold._record_equals(this, other, key_fields); + }; + }, + + _in_array: function(element, array, key_fields) + { + if (key_fields.length > 1) { + for (i = 0; i < array.length; i++) { + if (manifold._record_equals(element, array[i], key_fields)) + return true; + } + return false; + } else { + // XXX TODO If we have a dict, extract the key first + return ($.inArray(element, array) != -1); + } + }, + /************************************************************************** * Metadata management **************************************************************************/ @@ -221,6 +784,14 @@ var manifold = { return (typeof table.column === 'undefined') ? null : table.column; }, + get_field_names: function(method) + { + var columns = this.get_columns(method); + if (!columns) + return null; + return $.map(columns, function (x) { return x.name }); + }, + get_key: function(method) { var table = this.get_table(method); if (!table) @@ -247,6 +818,12 @@ var manifold = { if (!table) return null; + var match = $.grep(table.column, function(x) { return x.name == name }); + if (match.length == 0) { + return undefined; + } else { + return match[0].type; + } return (typeof table.type === 'undefined') ? null : table.type; } @@ -277,6 +854,11 @@ var manifold = { // NEW API manifold.query_store.insert(query); + // Run + $(document).ready(function() { + manifold.run_query(query); + }); + // FORMER API if (query.analyzed_query == null) { query.analyze_subqueries(); @@ -316,21 +898,30 @@ var manifold = { } }, - run_query: function(query, callback) { + run_query: function(query, callback) + { // default value for callback = null if (typeof callback === 'undefined') callback = null; + var query_ext = manifold.query_store.find_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_INPROGRESS; + var query_json = JSON.stringify(query); - /* Nothing related to pubsub here... for the moment at least. */ - //query.iter_subqueries(function (sq) { - // manifold.raise_record_event(sq.query_uuid, IN_PROGRESS); - //}); + // Inform plugins about the progress + query.iter_subqueries(function (sq) { + var sq_query_ext = manifold.query_store.find_analyzed_query_ext(sq.query_uuid); + sq_query_ext.query_state = QUERY_STATE_INPROGRESS; + + manifold.raise_record_event(sq.query_uuid, IN_PROGRESS); + }); + $.post(manifold.proxy_url, {'json': query_json} , manifold.success_closure(query, null, callback)); }, + // XXX DEPRECATED // Executes all async. queries - intended for the javascript header to initialize queries // input queries are specified as a list of {'query_uuid': } // each plugin is responsible for managing its spinner through on_query_in_progress @@ -421,6 +1012,29 @@ var manifold = { messages.debug(".. publish_result (5) END q=" + query.__repr()); }, + store_records: function(query, records) { + // Store records + var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid); + if (query_ext.set_query_ext) { + // We have a domain query + // The results are stored in the corresponding set_query + manifold.query_store.set_records(query_ext.set_query_ext.query.query_uuid, records) + + } else if (query_ext.domain_query_ext) { + // We have a set query, it is only used to determine which objects are in the set, we should only retrieve the key + // Has it a domain query, and has it completed ? + $.each(records, function(i, record) { + var key = manifold.metadata.get_key(query.object); + var record_key = manifold.record_get_value(record, key); + manifold.query_store.set_record_state(query.query_uuid, record_key, STATE_SET, STATE_SET_IN); + }); + + } else { + // We have a normal query + manifold.query_store.set_records(query.query_uuid, records, STATE_SET_IN); + } + }, + /*! * Recursively publish result * \fn publish_result_rec(query, result) @@ -430,30 +1044,50 @@ var manifold = { * * Note: this function works on the analyzed query */ - publish_result_rec: function(query, result) { + publish_result_rec: function(query, records) { /* If the result is not unique, only publish the top query; * otherwise, publish the main object as well as subqueries * XXX how much recursive are we ? */ if (manifold.pubsub_debug) - messages.debug (">>>>> publish_result_rec " + query.object); + messages.debug (">>>>> publish_result_rec " + query.object); if (manifold.query_expects_unique_result(query)) { /* Also publish subqueries */ $.each(query.subqueries, function(object, subquery) { - manifold.publish_result_rec(subquery, result[0][object]); + manifold.publish_result_rec(subquery, records[0][object]); /* TODO remove object from result */ }); } if (manifold.pubsub_debug) messages.debug ("===== publish_result_rec " + query.object); - manifold.publish_result(query, result); + var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_DONE; + + this.store_records(query, records); + + var pub_query; + + if (query_ext.set_query_ext) { + if (query_ext.set_query_ext.query_state != QUERY_STATE_DONE) + return; + pub_query = query_ext.set_query_ext.query; + } else if (query_ext.domain_query_ext) { + if (query_ext.domain_query_ext.query_state != QUERY_STATE_DONE) + return; + pub_query = query; + } else { + pub_query = query; + } + // We can only publish results if the query (and its related domain query) is complete + manifold.publish_result(pub_query, records); if (manifold.pubsub_debug) messages.debug ("<<<<< publish_result_rec " + query.object); }, - setup_update_query: function(query, records) { + setup_update_query: function(query, records) + { // We don't prepare an update query if the result has more than 1 entry if (records.length != 1) return; @@ -462,6 +1096,10 @@ var manifold = { var record = records[0]; var update_query_ext = query_ext.update_query_ext; + + if (!update_query_ext) + return; + var update_query = update_query_ext.query; var update_query_ext = query_ext.update_query_ext; var update_query_orig = query_ext.update_query_orig_ext.query; @@ -480,13 +1118,7 @@ var manifold = { if (!subrecords) continue $.each(subrecords, function (i, subrecord) { - if (key.length == 1){ - key = key[0]; - sq_keys.push(subrecord[key]); - }else{ - // more than what's necessary, but should work - sq_keys.push(subrecord); - } + sq_keys.push(manifold.record_get_value(subrecord, key)); }); update_query.params[method] = sq_keys; update_query_orig.params[method] = sq_keys.slice(); @@ -502,10 +1134,47 @@ var manifold = { process_get_query_records: function(query, records) { this.setup_update_query(query, records); + + var query_ext = manifold.query_store.find_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_DONE; /* Publish full results */ - var tmp_query = manifold.find_query(query.query_uuid); - manifold.publish_result_rec(tmp_query.analyzed_query, records); + var tmp_query = manifold.query_store.find_analyzed_query(query.query_uuid); + manifold.publish_result_rec(tmp_query, records); + }, + + make_records: function(object, records) + { + $.each(records, function(i, record) { + manifold.make_record(object, record); + }); + }, + + make_record: function(object, record) + { + // To make an object a record, we just add the hash function + var key = manifold.metadata.get_key(object); + record.hashCode = manifold.record_hashcode(record, key.sort()); + record.equals = manifold.record_equals(record, key); + + // Looking after subrecords + for (var field in record) { + var result_value = record[field]; + + switch (this.get_type(result_value)) { + case TYPE_RECORD: + var subobject = manifold.metadata.get_type(object, field); + // if (subobject) XXX Bugs with fields declared string while they are not : network.version is a dict in fact + if (subobject && subobject != 'string') + manifold.make_record(subobject, result_value); + break; + case TYPE_LIST_OF_RECORDS: + var subobject = manifold.metadata.get_type(object, field); + if (subobject) + manifold.make_records(subobject, result_value); + break; + } + } }, /** @@ -534,7 +1203,8 @@ var manifold = { // Let's iterate over the object properties for (var field in record) { - switch (this.get_type(record[field])) { + var result_value = record[field]; + switch (this.get_type(result_value)) { case TYPE_VALUE: // Did we ask for a change ? var update_value = update_query[field]; @@ -544,15 +1214,14 @@ var manifold = { // We assume it won't have changed continue; - var result_value = record[field]; if (!result_value) throw "Internal error"; data = { - request: FIELD_REQUEST_CHANGE, + state : STATE_SET, key : field, - value : update_value, - status: (update_value == result_value) ? FIELD_REQUEST_SUCCESS : FIELD_REQUEST_FAILURE, + op : update_value, + value : (update_value == result_value) ? STATE_VALUE_CHANGE_SUCCESS : STATE_VALUE_CHANGE_FAILURE, } manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data); @@ -561,9 +1230,9 @@ var manifold = { throw "Not implemented"; break; - case TYPE_LIST_OF_VALUES: + /* +case TYPE_LIST_OF_VALUES: // Same as list of records, but we don't have to extract keys - var result_keys = record[field] // The rest of exactly the same (XXX factorize) var update_keys = update_query_orig.params[field]; @@ -573,7 +1242,7 @@ var manifold = { $.each(added_keys, function(i, key) { - if ($.inArray(key, result_keys) == -1) { + if ($.inArray(key, result_value) == -1) { data = { request: FIELD_REQUEST_ADD, key : field, @@ -611,7 +1280,13 @@ var manifold = { break; + */ + case TYPE_LIST_OF_VALUES: // XXX Until fixed case TYPE_LIST_OF_RECORDS: + var new_state,cur_query_uuid; + + cur_query_uuid = query.analyzed_query.subqueries[field].query_uuid; + // example: slice.resource // - update_query_orig.params.resource = resources in slice before update // - update_query.params.resource = resource requested in slice @@ -619,65 +1294,67 @@ var manifold = { var key = manifold.metadata.get_key(field); if (!key) continue; + /* if (key.length > 1) { throw "Not implemented"; continue; } key = key[0]; + */ /* XXX should be modified for multiple keys */ - var result_keys = $.map(record[field], function(x) { return x[key]; }); + var result_keys = $.map(record[field], function(x) { return manifold.record_get_value(x, key); }); + // XXX All this could be deduced from record state : STATE_IN_PENDING and STATE_OUT_PENDING + // what we had at the begining var update_keys = update_query_orig.params[field]; + // what we asked var query_keys = update_query.params[field]; - var added_keys = $.grep(query_keys, function (x) { return $.inArray(x, update_keys) == -1 }); - var removed_keys = $.grep(update_keys, function (x) { return $.inArray(x, query_keys) == -1 }); + // what we added and removed + var added_keys = $.grep(query_keys, function (x) { return (!(manifold._in_array(x, update_keys, key))); }); + var removed_keys = $.grep(update_keys, function (x) { return (!(manifold._in_array(x, query_keys, key))); }); + // Send events related to parent query + $.each(added_keys, function(i, added_key) { + new_state = (manifold._in_array(added_key, result_keys, key)) ? STATE_SET_IN_SUCCESS : STATE_SET_IN_FAILURE; - $.each(added_keys, function(i, key) { - if ($.inArray(key, result_keys) == -1) { - data = { - request: FIELD_REQUEST_ADD, - key : field, - value : key, - status: FIELD_REQUEST_FAILURE, - } - } else { - data = { - request: FIELD_REQUEST_ADD, - key : field, - value : key, - status: FIELD_REQUEST_SUCCESS, - } - } + // Update record state for children queries + manifold.query_store.set_record_state(cur_query_uuid, added_key, STATE_SET, new_state); + + // XXX This could be optimized + manifold.query_store.recount(cur_query_uuid); + + data = { state: STATE_SET, key : field, op : new_state, value: added_key } manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data); }); - $.each(removed_keys, function(i, key) { - if ($.inArray(key, result_keys) == -1) { - data = { - request: FIELD_REQUEST_REMOVE, - key : field, - value : key, - status: FIELD_REQUEST_SUCCESS, - } - } else { - data = { - request: FIELD_REQUEST_REMOVE, - key : field, - value : key, - status: FIELD_REQUEST_FAILURE, - } - } + $.each(removed_keys, function(i, removed_key) { + new_state = (manifold._in_array(removed_key, result_keys, key)) ? STATE_SET_OUT_FAILURE : STATE_SET_OUT_SUCCESS; + + // Update record state for children queries + manifold.query_store.set_record_state(cur_query_uuid, removed_key, STATE_SET, new_state); + + // XXX This could be optimized + manifold.query_store.recount(cur_query_uuid); + + data = { state: STATE_SET, key : field, op : new_state, value: removed_key } manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data); }); - break; } } // XXX Now we need to adapt 'update' and 'update_orig' queries as if we had done a get this.setup_update_query(query, records); + + var query_ext = manifold.query_store.find_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_DONE; + + // Send DONE message to plugins + query.iter_subqueries(function(sq, data, parent_query) { + manifold.raise_record_event(sq.query_uuid, DONE); + }); + }, process_query_records: function(query, records) { @@ -735,6 +1412,7 @@ var manifold = { var result=data.value; if (result) { /* Eventually update the content of related queries (update, etc) */ + manifold.make_records(query.object, result); this.process_query_records(query, result); /* Publish results: disabled here, done in the previous call */ @@ -787,14 +1465,211 @@ var manifold = { manifold.raise_event_handler('record', query_uuid, event_type, value); }, + /** + * Event handler helpers + */ + _get_next_state_add: function(prev_state) + { + switch (prev_state) { + case STATE_SET_OUT: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + new_state = STATE_SET_IN_PENDING; + break; + + case STATE_SET_OUT_PENDING: + new_state = STATE_SET_IN; + break; + + case STATE_SET_IN: + case STATE_SET_IN_PENDING: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + console.log("Inconsistent state: already in"); + return; + } + return new_state; + }, + + _get_next_state_remove: function(prev_state) + { + switch (prev_state) { + case STATE_SET_IN: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + new_state = STATE_SET_OUT_PENDING; + break; + + case STATE_SET_IN_PENDING: + new_state = STATE_SET_OUT; + break; + + case STATE_SET_OUT: + case STATE_SET_OUT_PENDING: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + console.log("Inconsistent state: already out"); + return; + } + return new_state; + }, + + _grep_active_lease_callback: function(lease_query, resource_key) { + return function(lease_key_lease) { + var state, lease_key, lease; + + lease_key = lease_key_lease[0]; + lease = lease_key_lease[1]; + + if (lease['resource'] != resource_key) + return false; + + state = manifold.query_store.get_record_state(lease_query.query_uuid, lease_key, STATE_SET);; + switch(state) { + case STATE_SET_IN: + case STATE_SET_IN_PENDING: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + return true; + case STATE_SET_OUT: + case STATE_SET_OUT_PENDING: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + return false; + } + } + }, + + _enforce_constraints: function(query_ext, record, resource_key, event_type) + { + var query, data; + + query = query_ext.query; + + switch(query.object) { + + case 'resource': + // CONSTRAINT_RESERVABLE_LEASE + // + // +) If a reservable node is added to the slice, then it should have a corresponding lease + // XXX Not always a resource + var is_reservable = (record.exclusive == true); + if (is_reservable) { + var warnings = manifold.query_store.get_record_state(query.query_uuid, resource_key, STATE_WARNINGS); + + if (event_type == STATE_SET_ADD) { + // We should have a lease_query associated + var lease_query = query_ext.parent_query_ext.query.subqueries['lease']; // in options + var lease_query_ext = manifold.query_store.find_analyzed_query_ext(lease_query.query_uuid); + // Do we have lease records (in) with this resource + var lease_records = $.grep(lease_query_ext.records.entries(), this._grep_active_lease_callback(lease_query, resource_key)); + if (lease_records.length == 0) { + // Sets a warning + // XXX Need for a better function to manage warnings + var warn = CONSTRAINT_RESERVABLE_LEASE_MSG; + warnings[CONSTRAINT_RESERVABLE_LEASE] = warn; + } else { + // Lease are defined, delete the warning in case it was set previously + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } + } else { + // Remove warnings attached to this resource + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } + + manifold.query_store.set_record_state(query.query_uuid, resource_key, STATE_WARNINGS, warnings); + } + + /* This was redundant */ + // manifold.query_store.recount(query.query_uuid); + + // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark) + data = { + state: STATE_WARNINGS, + key : resource_key, + op : null, + value : warnings + } + manifold.raise_record_event(query.query_uuid, FIELD_STATE_CHANGED, data); + break; + + case 'lease': + var resource_key = record.resource; + var resource_query = query_ext.parent_query_ext.query.subqueries['resource']; + var warnings = manifold.query_store.get_record_state(resource_query.query_uuid, resource_key, STATE_WARNINGS); + + if (event_type == STATE_SET_ADD) { + // A lease is added, it removes the constraint + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } else { + // A lease is removed, it might trigger the warning + var lease_records = $.grep(query_ext.records.entries(), this._grep_active_lease_callback(query, resource_key)); + if (lease_records.length == 0) { // XXX redundant cases + // Sets a warning + // XXX Need for a better function to manage warnings + var warn = CONSTRAINT_RESERVABLE_LEASE_MSG; + warnings[CONSTRAINT_RESERVABLE_LEASE] = warn; + } else { + // Lease are defined, delete the warning in case it was set previously + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } + + } + + manifold.query_store.recount(resource_query.query_uuid); + + // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark) + data = { + state: STATE_WARNINGS, + key : resource_key, + op : null, + value : warnings + } + manifold.raise_record_event(resource_query.query_uuid, FIELD_STATE_CHANGED, data); + break; + } + + // -) When a lease is added, it might remove the warning associated to a reservable node + + // If a NITOS node is reserved, then at least a NITOS channel should be reserved + // - When a NITOS channel is added, it might remove a warning associated to all NITOS nodes + + // If a NITOS channel is reserved, then at least a NITOS node should be reserved + // - When a NITOS node is added, it might remove a warning associated to all NITOS channels + + // A lease is present while the resource has been removed => Require warnings on nodes not in set ! + + }, + + _get_query_path: function(query_ext) { + var path = ""; + var sq = query_ext; + while (sq.parent_query_ext) { + if (path != "") + path = '.' + path; + path = sq.query.object + path; + sq = sq.parent_query_ext; + } + return path; + }, + + + /** + * Handling events raised by plugins + */ + raise_event: function(query_uuid, event_type, data) + { + var query, query_ext; - raise_event: function(query_uuid, event_type, value) { // Query uuid has been updated with the key of a new element query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid); query = query_ext.query; switch(event_type) { + + // XXX At some point, should be renamed to RECORD_STATE_CHANGED case FIELD_STATE_CHANGED: + // value is an object (request, key, value, status) // update is only possible is the query is not pending, etc // SET_ADD is on a subquery, FIELD_STATE_CHANGED on the query itself @@ -806,104 +1681,105 @@ var manifold = { update_query = query_ext.main_query_ext.update_query_ext.query; update_query_orig = query_ext.main_query_ext.update_query_orig_ext.query; - switch(value.request) { - case FIELD_REQUEST_CHANGE: - if (update_query.params[value.key] === undefined) - update_query.params[value.key] = Array(); - update_query.params[value.key] = value.value; - break; - case FIELD_REQUEST_ADD: - if ($.inArray(value.value, update_query_orig.params[value.key]) != -1) - value.request = FIELD_REQUEST_ADD_RESET; - if (update_query.params[value.key] === undefined) - update_query.params[value.key] = Array(); - update_query.params[value.key].push(value.value); + switch(data.state) { + + case STATE_VALUE: + switch(data.op) { + case STATE_CHANGE: + /* Set parameter data.key in the update_query to VALUE */ + if (update_query.params[data.key] === undefined) + update_query.params[data.key] = Array(); + update_query.params[data.key] = value.value; + break; + + } break; - case FIELD_REQUEST_REMOVE: - if ($.inArray(value.value, update_query_orig.params[value.key]) == -1) - value.request = FIELD_REQUEST_REMOVE_RESET; - var arr = update_query.params[value.key]; - arr = $.grep(arr, function(x) { return x != value.value; }); - if (update_query.params[value.key] === undefined) - update_query.params[value.key] = Array(); - update_query.params[value.key] = arr; + case STATE_SET: + var prev_state, new_state; + var main_query, record, new_data, path; + + // We only track state in the analyzed query + prev_state = manifold.query_store.get_record_state(query_uuid, data.value, STATE_SET); + if (prev_state === null) + prev_state = STATE_SET_OUT; + + switch(data.op) { + case STATE_SET_ADD: + new_state = this._get_next_state_add(prev_state); + + /* data.value containts the resource key */ + manifold.query_store.add_record(query_uuid, data.value, new_state); + record = manifold.query_store.get_record(query_uuid, data.value); + this._enforce_constraints(query_ext, record, data.value, STATE_SET_ADD); + + /* Process update query in parent */ + path = this._get_query_path(query_ext); + if (update_query.params[path] === undefined) + update_query.params[path] = Array(); + update_query.params[path].push(data.value); + + break; + + case STATE_SET_REMOVE: + new_state = this._get_next_state_remove(prev_state); + + /* data.value contains the resource key */ + manifold.query_store.remove_record(query_uuid, data.value, new_state); + record = manifold.query_store.get_record(query_uuid, data.value); + this._enforce_constraints(query_ext, record, data.value, STATE_SET_REMOVE); + + /* Process update query in parent */ + path = this._get_query_path(query_ext); + arr = update_query.params[path]; + + var key = manifold.metadata.get_key(query.object); + arr = $.grep(arr, function(x) { return (!(manifold._record_equals(x, data.value, key))); }); + if (update_query.params[path] === undefined) + update_query.params[path] = Array(); + update_query.params[path] = arr; + break; + } + + /* Inform the parent query: important for update */ + new_data = { + state : STATE_SET, + key : path, + op : new_state, + value : data.value, + }; + main_query = query_ext.main_query_ext.query; + manifold.raise_record_event(main_query.query_uuid, event_type, new_data); + /* Propagate the event to other plugins subscribed to the query */ + manifold.query_store.recount(query_uuid); + new_data.key = '' + manifold.raise_record_event(query_uuid, event_type, new_data); - break; - case FIELD_REQUEST_ADD_RESET: - case FIELD_REQUEST_REMOVE_RESET: - // XXX We would need to keep track of the original query - throw "Not implemented"; break; } - +/* // 3. Inform others about the change // a) the main query... - manifold.raise_record_event(query_uuid, event_type, value); + manifold.raise_record_event(query_uuid, event_type, data); // b) subqueries eventually (dot in the key) // Let's unfold - var path_array = value.key.split('.'); - var value_key = value.key.split('.'); var cur_query = query; if (cur_query.analyzed_query) cur_query = cur_query.analyzed_query; - $.each(path_array, function(i, method) { - cur_query = cur_query.subqueries[method]; - value_key.shift(); // XXX check that method is indeed shifted - }); - value.key = value_key; - - manifold.raise_record_event(cur_query.query_uuid, event_type, value); - // XXX make this DOT a global variable... could be '/' - break; - - case SET_ADD: - case SET_REMOVED: - - // update is only possible is the query is not pending, etc - // CHECK status ! - - // XXX we can only update subqueries of the main query. Check ! - // assert query_ext.parent_query == query_ext.main_query - // old // update_query = query_ext.main_query_ext.update_query_ext.query; - - // This SET_ADD is called on a subquery, so we have to - // recontruct the path of the key in the main_query - // We then call FIELD_STATE_CHANGED which is the equivalent for the main query - - var path = ""; - var sq = query_ext; - while (sq.parent_query_ext) { - if (path != "") - path = '.' + path; - path = sq.query.object + path; - sq = sq.parent_query_ext; + if (data.key) { + var path_array = data.key.split('.'); + var value_key = data.key.split('.'); + $.each(path_array, function(i, method) { + cur_query = cur_query.subqueries[method]; + value_key.shift(); // XXX check that method is indeed shifted + }); + data.key = value_key; } - - main_query = query_ext.main_query_ext.query; - data = { - request: (event_type == SET_ADD) ? FIELD_REQUEST_ADD : FIELD_REQUEST_REMOVE, - key : path, - value : value, - status: FIELD_REQUEST_PENDING, - }; - this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, data); - - // old //update_query.params[path].push(value); - // old // console.log('Updated query params', update_query); - // NOTE: update might modify the fields in Get - // NOTE : we have to modify all child queries - // NOTE : parts of a query might not be started (eg slice.measurements, how to handle ?) - - // if everything is done right, update_query should not be null. - // It is updated when we received results from the get query - // object = the same as get - // filter = key : update a single object for now - // fields = the same as get - manifold.raise_query_event(query_uuid, event_type, value); + manifold.raise_record_event(cur_query.query_uuid, event_type, data); +*/ break; @@ -911,38 +1787,54 @@ var manifold = { manifold.run_query(query_ext.main_query_ext.update_query_ext.query); break; + /* QUERY STATE CHANGED */ + + // FILTERS + case FILTER_ADDED: - manifold.raise_query_event(query_uuid, event_type, value); + /* Update internal record state */ + manifold.query_store.add_filter(query_uuid, data); + + /* Propagate the message to plugins */ + manifold.raise_query_event(query_uuid, event_type, data); + break; + case FILTER_REMOVED: - manifold.raise_query_event(query_uuid, event_type, value); + /* Update internal record state */ + manifold.query_store.remove_filter(query_uuid, data); + + /* Propagate the message to plugins */ + manifold.raise_query_event(query_uuid, event_type, data); + break; + case FIELD_ADDED: main_query = query_ext.main_query_ext.query; main_update_query = query_ext.main_query_ext.update_query; - query.select(value); + query.select(data); // Here we need the full path through all subqueries path = "" // XXX We might need the query name in the QueryExt structure - main_query.select(value); + main_query.select(data); // XXX When is an update query associated ? // XXX main_update_query.select(value); - manifold.raise_query_event(query_uuid, event_type, value); + manifold.raise_query_event(query_uuid, event_type, data); break; case FIELD_REMOVED: query = query_ext.query; main_query = query_ext.main_query_ext.query; main_update_query = query_ext.main_query_ext.update_query; - query.unselect(value); - main_query.unselect(value); + query.unselect(data); + main_query.unselect(data); // We need to inform about changes in these queries to the respective plugins // Note: query & main_query have the same UUID - manifold.raise_query_event(query_uuid, event_type, value); + manifold.raise_query_event(query_uuid, event_type, data); break; } // We need to inform about changes in these queries to the respective plugins diff --git a/manifoldapi/static/js/plugin.js b/manifoldapi/static/js/plugin.js index 24d41129..76e1cb52 100644 --- a/manifoldapi/static/js/plugin.js +++ b/manifoldapi/static/js/plugin.js @@ -1,3 +1,24 @@ +// Common parts for angularjs plugins +// only one ng-app is allowed + +var ManifoldApp = angular.module('ManifoldApp', []); +ManifoldApp.config(function ($interpolateProvider) { + $interpolateProvider.startSymbol('{[{').endSymbol('}]}'); +}); + +ManifoldApp.factory('$exceptionHandler', function () { + return function (exception, cause) { + console.log(exception.message); + }; +}); + +ManifoldApp.filter('offset', function() { + return function(input, start) { + start = parseInt(start, 10); + return input.slice(start); + }; +}); + // INHERITANCE // http://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/ // We will use John Resig's proposal @@ -220,7 +241,7 @@ var Plugin = Class.extend({ id_from_key: function(key_field, value) { - return key_field + manifold.separator + this.escape_id(value).replace(/\\/g, ''); + return key_field + manifold.separator + this.escape_id(value); //.replace(/\\/g, ''); }, // NOTE diff --git a/myslice/urls.py b/myslice/urls.py index a49156e1..68ec521a 100644 --- a/myslice/urls.py +++ b/myslice/urls.py @@ -16,7 +16,7 @@ import portal.platformsview import portal.dashboardview import portal.homeview import portal.newsview - +from portal.registrationview import RegistrationView home_view=portal.homeview.HomeView.as_view() dashboard_view=portal.dashboardview.DashboardView.as_view() platforms_view=portal.platformsview.PlatformsView.as_view() @@ -31,6 +31,8 @@ import portal.slicetabtestbeds import portal.slicetabusers import portal.slicetabmeasurements +import portal.managementtabrequests + #### high level choices # main entry point (set to the / URL) # beware that if this view is broken you end up in an endless cycle... @@ -90,6 +92,10 @@ urls = [ (r'^testbeds/(?P[^/]+)/?$', portal.slicetabtestbeds.SliceTabTestbeds.as_view()), (r'^measurements/(?P[^/]+)/?$', portal.slicetabmeasurements.SliceTabMeasurements.as_view()), (r'^experiment/(?P[^/]+)/?$', portal.slicetabexperiment.ExperimentView.as_view()), + # + (r'^management/requests/?$', portal.managementtabrequests.ManagementRequestsView.as_view()), + # + url(r'^register/?$', RegistrationView.as_view(), name='registration'), url(r'^portal/', include('portal.urls')), # SLA diff --git a/plugins/apply/__init__.py b/plugins/apply/__init__.py new file mode 100644 index 00000000..540cf01d --- /dev/null +++ b/plugins/apply/__init__.py @@ -0,0 +1,51 @@ +from unfold.plugin import Plugin +from plugins.queryupdater import QueryUpdaterPlugin + +class ApplyPlugin(Plugin): + + def __init__ (self, query=None, **settings): + Plugin.__init__ (self, **settings) + self.query = query + self.username = str(settings['username']) + + def template_file (self): + return "apply.html" +# +# def template_env(self, request): +# query_updater = QueryUpdaterPlugin( +# page = self.page, +# title = 'Pending operations', +# query = self.query, +# togglable = False, +# # start turned off, it will open up itself when stuff comes in +# toggled = True, +# domid = 'pending', +# outline_complete = True, +# username = request.user, # XXX ??? +# ) +# +# env = Plugin.template_env(self, request) +# env.update({'query_updater': query_updater.render(request)}) +# return env + + def requirements (self): + reqs = { + 'js_files' : [ + 'js/dataTables.js', + 'js/apply.js' + ], + 'css_files' : [ + 'css/dataTables.bootstrap.css', + 'css/apply.css' + ], + } + return reqs + + def json_settings_list (self): + # query_uuid will pass self.query results to the javascript + # and will be available as "record" in : + # on_new_record: function(record) + return ['plugin_uuid', 'domid', 'query_uuid', 'username'] + + def export_json_settings (self): + return True diff --git a/plugins/apply/static/css/apply.css b/plugins/apply/static/css/apply.css new file mode 100644 index 00000000..1577f71a --- /dev/null +++ b/plugins/apply/static/css/apply.css @@ -0,0 +1,31 @@ +.modal-dialog-large { + width: 800px; +} + +div.resources-selected-spacer { + margin: 20px 15px; +} +table.resources-selected { + width: 100%; + margin: 10px; +} +table.resources-selected th { + background: url('../img/tablesort-header.png') repeat-x; + font-weight: normal; + font-style: italic; +} +tr.add td{ + background-color: #E3F6CE; +} +tr.remove td{ + background-color: #F6CECE; +} + +.ResourceSelectedClose{ + cursor: pointer; +} + +#pending__table_paginate { + margin-bottom: 10px; +} + diff --git a/plugins/apply/static/js/apply.js b/plugins/apply/static/js/apply.js new file mode 100644 index 00000000..efc93450 --- /dev/null +++ b/plugins/apply/static/js/apply.js @@ -0,0 +1,498 @@ +/** + * MySlice Apply plugin + * Version: 0.1.0 + * URL: http://www.myslice.info + * Description: display of selected resources + * Requires: + * Author: The MySlice Team + * Copyright: Copyright 2012 UPMC Sorbonne Universités + * License: GPLv3 + */ + +(function( $ ){ + + var debug=false; + debug=true; + + // XXX record selected (multiple selection ?) + // XXX record disabled ? + // XXX out of sync plugin ? + // XXX out of date record ? + // record tags ??? + // + // criticality of the absence of a handler in a plugin + // non-critical only can have switch case + // + // Record state through the query cycle + + + var ApplyPlugin = Plugin.extend({ + + /************************************************************************** + * CONSTRUCTOR + **************************************************************************/ + + init: function(options, element) { + this.classname="queryupdater"; + this._super(options, element); + + var self = this; + + this.initial = Array(); + this.selected_resources = Array(); + + this.table = this.elmt('table').dataTable({ + sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t>", + bAutoWidth: true, + }); + + //this.elmt('update').click(this, this.do_ok); + //this.elmt('refresh').click(this, this.do_cancel); + + this.elmt('apply').on('shown.bs.modal', function() { + self.do_update(); + }) + + this.listen_query(options.query_uuid); + }, + + /*************************** PLUGIN EVENTS ****************************/ + + /***************************** GUI EVENTS *****************************/ + + /************************** GUI MANIPULATION **************************/ + + populate_table: function() + { + var state; + + // Loop over records and display pending ones + manifold.query_store.iter_records(this.options.query_uuid, function (record_key, record) { + state = manifold.query_store.get_record_state(this.options.query_uuid, null, STATE_SET); + + }); + }, + + set_button_state: function(name, state) + { + this.elmt(name).attr('disabled', state ? false : 'disabled'); + }, + + clear: function() + { + + }, + + find_row: function(value) + { + var KEY_POS, VALUE_POS, data, object_type, object_key; + + TYPE_POS = 1; + // key in third position, column id = 2 + VALUE_POS = 2; + + var cols = $.grep(this.table.fnSettings().aoData, function(col) { + cur_value = col._aData[VALUE_POS]; + object_type = col._aData[TYPE_POS]; + object_key = manifold.metadata.get_key(object_type); + + if (cur_value[0] == '{') { + cur_value = JSON.parse(cur_value); + return manifold._record_equals(cur_value, value, object_key);_ + } else { + return (cur_value == value); + } + } ); + + if (cols.length == 0) + return null; + if (cols.length > 1) + throw "Too many same-key rows in ResourceSelected plugin"; + + return cols[0]; + }, + + _do_update: function(e) { + var self = e.data; + self.do_update; + }, + + do_update: function() { + + var username = this.options.username; + + this.spin(); + console.log("do_update in progress"); + + manifold.raise_event(this.options.query_uuid, RUN_UPDATE); + + // how to stop the spinning after the event? + // this should be triggered by some on_updatequery_done ? + + }, + + do_ok: function(e) + { + throw 'queryupdater.do_reset Not implemented'; + }, + + do_cancel: function(e) + { + throw 'queryupdater.do_clear_annotations Not implemented'; + }, + + /************************************************************************** + * QUERY HANDLERS + **************************************************************************/ + + on_new_record: function(record) + { + + // if (not and update) { + + // initial['resource'], initial['lease'] ? + this.initial.push(record); + + //this.set_record_state(record, RECORD_STATE_ATTACHED); + // We simply add to the table + // } else { + // \ this.initial_resources + // \ + // this. \ + // current_resources \ YES | NO + // --------------------+----------+--------- + // YES | attached | added + // NO | removed | / + // + + // } + }, + + // QUERY STATUS + // +-----------------+--------------+ + // v R | | + // +-------+ ?G +-------+ +-------+ +---+---+ | + // | | -----> | | !G | | | | DA | + // | ND | | PG | -----> | D | -----> | PC | <------+ | + // | | <----- | | ~G | | C | | | | + // +-------+ GE +-------+ +-------+ +-------+ +------+ + // ^ ^ | | | + // | DA UE | | ?U | PCA | + // | | v | | + // +-------+ +-------+ +------+ + // | | !U | | ^ + // | AD | <----- | PU | --------+ + // | | | | ~U + // +-------+ +-------+ + // + // + // LEGEND: + // + // Plugins (i) receive state information, (ii) perform actions + // + // States: Actions: + // ND : No data ?G : Get query + // PG : Pending Get !G : Get reply + // D : Data present ~G : Get partial reply + // PC : Pending changes GE : Get error + // PU : Pending update C : Change request + // PCA: Pending change with annotation R : Reset request + // AD : Annotated data ?U : Update query + // !U : Update reply + // ~U : Update partial reply + // UE : Update error + // DA : Delete annotation request + // NOTE: + // - D -> PU directly if the user chooses 'dynamic update' + // - Be careful for updates if partial Get success + + // ND: No data == initialization state + + // PG : Pending get + // - on_query_in_progress + // NOTE: cannot distinguish get and update here. Is it important ? + + on_query_in_progress: function() + { + this.spin(); + }, + + on_query_done: function() + { + this.populate_table(); + this.unspin(); + }, + + // D : Data present + // - on_clear_records (Get) + // - on_new_record (shared with AD) XXX + // - on_query_done + // NOTE: cannot distinguish get and update here. Is it important ? + // NOTE: Would record key be sufficient for update ? + + on_clear_records: function() + { + this.clear(); + }, + + on_query_done: function() + { + this.unspin(); + }, + + // PC : Pending changes + // NOTE: record_key could be sufficient + on_added_record: function(record) + { + this.set_record_state(record, RECORD_STATE_ADDED); + // update pending number + }, + + on_removed_record: function(record_key) + { + this.set_record_state(RECORD_STATE_REMOVED); + }, + + // PU : Pending update + // - on_query_in_progress (already done) + + // PCA : Pending change with annotation + // NOTE: Manifold will inform the plugin about updates, and thus won't + // call new record, even if the same channel UUID is used... + // - TODO on_updated_record + // - Key and confirmation could be sufficient, or key and record state + // XXX move record state to the manifold plugin API + + on_field_state_changed: function(data) + { + /* + if(result.request == FIELD_REQUEST_ADD){ + this.selected_resources.push(result.value); + } else if(result.request == FIELD_REQUEST_REMOVE_RESET){ + var i = this.selected_resources.indexOf(result.value); + if(i != -1){ + this.selected_resources.splice(i,1); + } + } + this.set_state(result); + */ + + var action, msg, row, status, button = ''; + + switch(data.state) { + case STATE_VALUE: + switch(data.op) { + // XXX other events missing !! + case STATE_VALUE_CHANGE_PENDING: + action = 'UPDATE'; + break; + } + break; + + case STATE_SET: + switch(data.op) { + case STATE_SET_IN_PENDING: + action = 'ADD'; + msg = 'PENDING'; + button = ""; + break; + + case STATE_SET_OUT_PENDING: + action = 'REMOVE'; + msg = 'PENDING'; + button = ""; + break; + + case STATE_SET_IN: + case STATE_SET_OUT: + // find line and delete it + row = this.find_row(data.value); + if (row) + this.table.fnDeleteRow(row.nTr); + return; + + case STATE_SET_IN_SUCCESS: + action = 'ADD'; + msg = 'SUCCESS'; + break; + case STATE_SET_IN_FAILURE: + action = 'ADD'; + msg = 'FAILURE'; + break; + + case STATE_SET_OUT_SUCCESS: + action = 'REMOVE'; + msg = 'SUCCESS'; + break; + case STATE_SET_OUT_FAILURE: + action = 'REMOVE'; + msg = 'FAILURE'; + break; + + } + break; + + default: + return; + } + + status = msg + status; + + // find line + // if no, create it, else replace it + // XXX it's not just about adding lines, but sometimes removing some + // XXX how do we handle status reset ? + + // Jordan : I don't understand this. I added this test otherwise we have string = ""..."" double quoted twice. + if (typeof(data.value) !== "string") + data.value = JSON.stringify(data.value); + data.selected_resources = this.selected_resources; + row = this.find_row(data.value); + newline = [action, data.key, data.value, msg, button]; + if (!row) { + // XXX second parameter refresh = false can improve performance. todo in querytable also + this.table.fnAddData(newline); + row = this.find_row(data.value); + } else { + // Update row text... + this.table.fnUpdate(newline, row.nTr); + } + + // Change cell color according to status + if (row) { + $(row.nTr).removeClass('add remove') + var cls = action.toLowerCase(); + if (cls) + $(row.nTr).addClass(cls); + } + }, + + // XXX we will have the requests for change + // XXX + the requests to move into the query cycle = the buttons aforementioned + + // XXX what happens in case of updates ? not implemented yet + // XXX we want resources and leases + // listen for SET_ADD and SET_REMOVE for slice query + + /************************** PRIVATE METHODS ***************************/ + + _close_click: function(e) + { + var self = e.data; + + //jQuery.publish('selected', 'add/'+key_value); + // this.parentNode is this.parentNode.parentNode is + // this.parentNode.parentNode.firstChild is the first cell of this line + // this.parentNode.parentNode.firstChild.firstChild is the text in that cell + //var firstCellVal=this.parentNode.parentNode.firstChild.firstChild.data; + var remove_urn = this.id; + var current_resources = event.data.instance.current_resources; + var list_resources = $.grep(current_resources, function(x) {return x.urn != remove_urn}); + //jQuery.publish('selected', 'cancel/'+this.id+'/'+unfold.get_value(firstCellVal)); + $.publish('/update-set/' + event.data.instance.options.resource_query_uuid, [list_resources, true]); + }, + + /******************************** TODO ********************************/ + + update_resources: function(resources, change) + { + messages.debug("update_resources"); + var my_oTable = this.table.dataTable(); + var prev_resources = this.current_resources; + + /* + * The first time the query is advertised, don't do anything. The + * component will learn nodes in the slice through the manifest + * received through the other subscription + */ + if (!change) + return; + // ioi: Refubrished + var initial = this.initial; + //var r_removed = []; // + /*----------------------------------------------------------------------- + TODO: remove this dirty hack !!! + */ + resources = jQuery.map(resources, function(x){ + if(!('timeslot' in x)){x.timeslot=0;} + return x; + }); + /* + TODO: handle generic keys instead of specific stuff + ex: urn + urn-lease + */ + var initial_urn = $.map(initial, function(x){return x.urn;}); + var resources_urn = $.map(resources, function(x){return x.urn;}); + var r_removed = $.grep(initial, function (x) { return $.inArray(x.urn, resources_urn) == -1 }); + var r_attached = $.grep(initial, function (x) { return $.inArray(x.urn, resources_urn) > -1 }); + var r_added = $.grep(resources, function (x) { return $.inArray(x.urn, initial_urn) == -1 }); + exists = false; // ioi + /*-----------------------------------------------------------------------*/ + + my_oTable.fnClearTable(); + /* + TODO: factorization of this code !!! + */ + $.each(r_added, function(i, r) { + //var type = (typeof initial == 'undefined' || r.node != initial.node) ? 'add' : 'attached'; + var type = 'add'; + // Create the resource objects + // ioi: refubrished + var urn = r.urn; + time = r.timeslot; + + var SPAN = ""; + var slot = "" + time + ""; //ioi + // ioi + var newline=Array(); + newline.push(type, urn, slot, SPAN); // ioi + var line = my_oTable.fnAddData(newline); + var nTr = my_oTable.fnSettings().aoData[ line[0] ].nTr; + nTr.className = type; + }); + $.each(r_attached, function(i, r) { + //var type = (typeof initial == 'undefined' || r.node != initial.node) ? 'add' : 'attached'; + var type = 'attached'; + // Create the resource objects + // ioi: refubrished + var node = r.urn; + time = r.timeslot; + + var SPAN = ""; + var slot = "" + time + ""; //ioi + // ioi + var newline=Array(); + newline.push(type, node, slot, SPAN); // ioi + var line = my_oTable.fnAddData(newline); + var nTr = my_oTable.fnSettings().aoData[ line[0] ].nTr; + nTr.className = type; + }); + $.each(r_removed, function(i, r) { + // The list contains objects + // ioi: refubrished + var node = r.urn; + var time = r.timeslot; + + var SPAN = ""; + var slot = "" + time + ""; + // ioi + var newline=Array(); + newline.push('remove', node, slot, SPAN); // ioi + var line = my_oTable.fnAddData(newline); + var nTr = my_oTable.fnSettings().aoData[ line[0] ].nTr; + nTr.className = 'remove'; + }); + + this.current_resources = $.merge(r_attached,r_added); + + /* Allow the user to update the slice */ + //jQuery('#updateslice-' + data.ResourceSelected.plugin_uuid).prop('disabled', false); + + }, // update_resources + + }); + + $.plugin('ApplyPlugin', ApplyPlugin); + +})(jQuery); diff --git a/plugins/apply/templates/apply.html b/plugins/apply/templates/apply.html new file mode 100644 index 00000000..65070128 --- /dev/null +++ b/plugins/apply/templates/apply.html @@ -0,0 +1,41 @@ +
+ + + + + + + +
diff --git a/plugins/filter_status/__init__.py b/plugins/filter_status/__init__.py new file mode 100644 index 00000000..a0d269b3 --- /dev/null +++ b/plugins/filter_status/__init__.py @@ -0,0 +1,30 @@ +from unfold.plugin import Plugin + +class FilterStatusPlugin(Plugin): + + def __init__ (self, query=None, **settings): + Plugin.__init__ (self, **settings) + self.query = query + + def template_file (self): + return "filter_status.html" + + def requirements (self): + reqs = { + 'js_files' : [ + 'js/filter_status.js' + ], +# 'css_files': [ +# 'css/myplugin.css', +# ] + } + return reqs + + def json_settings_list (self): + # query_uuid will pass self.query results to the javascript + # and will be available as "record" in : + # on_new_record: function(record) + return ['plugin_uuid', 'domid', 'query_uuid'] + + def export_json_settings (self): + return True diff --git a/plugins/filter_status/static/js/filter_status.js b/plugins/filter_status/static/js/filter_status.js new file mode 100644 index 00000000..3bcd15c2 --- /dev/null +++ b/plugins/filter_status/static/js/filter_status.js @@ -0,0 +1,138 @@ +/** + * TestbedsPlugin: List of testbeds plugin + * Version: 0.1 + * Description: TODO -> generalize to a list of possible filters + * This file is part of the Manifold project + * Requires: js/plugin.js + * URL: http://www.myslice.info + * Author: Loïc Baron + * Copyright: Copyright 2012-2013 UPMC Sorbonne Universités + * License: GPLv3 + */ + +(function($){ + + var FilterStatusPlugin = Plugin.extend({ + + /** + * @brief Plugin constructor + * @param options : an associative array of setting values + * @param element : + * @return : a jQuery collection of objects on which the plugin is + * applied, which allows to maintain chainability of calls + */ + init: function(options, element) + { + // Call the parent constructor, see FAQ when forgotten + this._super(options, element); + + /* Setup query and record handlers */ + this.listen_query(options.query_uuid); + + /* Setup click handlers */ + this.elts('list-group-item').click({'instance': this}, this._on_click); + + this.prev_filter_status = null; + + /* Initialize tooltips */ + $("[rel='tooltip']").tooltip(); + + }, + + /************************************************************************** + * GUI MANAGEMENT * + **************************************************************************/ + + select_tab: function(tab) + { + this.elts('list-group-item').removeClass('active'); + this.elmt(tab).addClass('active'); + }, + + /************************************************************************** + * EVENT HANDLERS * + **************************************************************************/ + + // These functions are here to react on external filters, which we don't + // use at the moment + + on_filter_added: function(filter) + { + // XXX + }, + + on_filter_removed: function(filter) + { + // XXX + }, + + on_field_state_changed: function(data) + { + var query_ext; + + switch (data.state) { + case STATE_SET: + case STATE_WARNINGS: + /* Get the number of pending / unconfigured resources */ + /* Let's store it in query_ext */ + query_ext = manifold.query_store.find_analyzed_query_ext(this.options.query_uuid); + + $('#badge-pending').text(query_ext.num_pending); + if (query_ext.num_pending > 0) { + $('#badge-pending').show(); + } else { + $('#badge-pending').hide(); + } + + $('#badge-unconfigured').text(query_ext.num_unconfigured); + if (query_ext.num_unconfigured > 0) { + $('#badge-unconfigured').show(); + } else { + $('#badge-unconfigured').hide(); + } + default: + break; + } + }, + + /************************************************************************** + * PRIVATE METHODS * + **************************************************************************/ + + /** + * @brief : Click event handler + */ + _on_click: function(e) + { + var filter_status; + var filter; + + // A pointer to the plugin instance, since 'this' is overriden here + self = e.data.instance; + + + // Select the tab... + filter_status = this.dataset['status']; + self.select_tab(filter_status); + + // ... and communicate the appropriate filters to the manager + // NOTE: we use the manifold namespace for internal filters + if (self.prev_filter_status) + manifold.raise_event(self.options.query_uuid, FILTER_REMOVED, self.prev_filter_status); + + // XXX The datatables will be refreshed twice ! + if (filter_status != 'all') { + // No filter for 'all' + var filter = ['manifold:status', '==', filter_status]; + manifold.raise_event(self.options.query_uuid, FILTER_ADDED, filter); + } + + self.prev_filter_status = filter_status; + }, + + }); + + /* Plugin registration */ + $.plugin('FilterStatusPlugin', FilterStatusPlugin); + +})(jQuery); diff --git a/plugins/filter_status/templates/filter_status.html b/plugins/filter_status/templates/filter_status.html new file mode 100644 index 00000000..498c1490 --- /dev/null +++ b/plugins/filter_status/templates/filter_status.html @@ -0,0 +1,43 @@ + diff --git a/plugins/googlemap/__init__.py b/plugins/googlemap/__init__.py index a769f7f8..08f2a7bc 100644 --- a/plugins/googlemap/__init__.py +++ b/plugins/googlemap/__init__.py @@ -8,12 +8,10 @@ class GoogleMap (Plugin): # googlemap_key : mandatory googlemap API v3 key # latitude,longitude, zoom : the starting point # apparently at some point there has been support for a boolean 'checkboxes' input arg but seems dropped - def __init__ (self, query, query_all, googlemap_api_key=None, latitude=43., longitude=7., zoom=4, **settings): + def __init__ (self, query, googlemap_api_key=None, latitude=43., longitude=7., zoom=4, **settings): Plugin.__init__ (self, **settings) self.query=query - self.query_all = query_all self.googlemap_api_key=googlemap_api_key - self.query_all_uuid = query_all.query_uuid if query_all else None self.latitude=latitude self.longitude=longitude self.zoom=zoom @@ -45,7 +43,7 @@ class GoogleMap (Plugin): # the list of things passed to the js plugin def json_settings_list (self): - return [ 'plugin_uuid', 'query_uuid', 'query_all_uuid', + return [ 'plugin_uuid', 'query_uuid', 'init_key', 'latitude', 'longitude', 'zoom', ] diff --git a/plugins/googlemap/static/js/googlemap.js b/plugins/googlemap/static/js/googlemap.js index 8a740b2a..5a15a3d8 100644 --- a/plugins/googlemap/static/js/googlemap.js +++ b/plugins/googlemap/static/js/googlemap.js @@ -8,6 +8,10 @@ * - infowindow is not properly reopened when the maps does not have the focus */ +GOOGLEMAP_BGCOLOR_RESET = 0; +GOOGLEMAP_BGCOLOR_ADDED = 1; +GOOGLEMAP_BGCOLOR_REMOVED = 2; + (function($){ // events that happen in the once-per-view range @@ -21,27 +25,30 @@ var GoogleMap = Plugin.extend({ - init: function(options, element) { - this.classname="googlemap"; + /************************************************************************** + * CONSTRUCTOR + **************************************************************************/ + + init: function(options, element) + { this._super(options, element); /* Member variables */ - // query status - this.received_all = false; - this.received_set = false; - this.in_set_backlog = []; - - // we keep a couple of global hashes - // lat_lon --> { marker,