(function() { var Ext = window.Ext4 || window.Ext; var UUID_FORMAT = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}|[a-fA-F0-9]{32}/; var PERMISSION_FORMAT = /\d+u\d+[pw]\d+/; var EXPLICIT_VERSION_FORMAT = /^v{0,1}\d+?\.\d+?$/; /** * Rally.util.Ref objects can be created to simplify working with reference URIs. * A reference URI is a unique identifier that references an object in your Rally subscription. * Constructing a Ref object parses the URI into its various components: * * - Type * - OID * - WSAPI Version * - TypeDefOid [only if a scoped attributedefinition reference] * - ScopeType [only if a scoped attributedefinition reference] * * Parse a WSAPI reference URI, WSAPI object, or {Rally.data.Model} record: * * // Create Ref object from relative ref URI string * * var ref = Ext.create('Rally.util.Ref', '/workspace/123'); // returns "workspace", 123 * * // Create Ref object from scoped relative ref URI string * * var ref = Ext.create('Rally.util.Ref', '/workspace/123/typedefinition/456/attributedefinition/789'); // returns "attributedefinition", 789, 'workspace', 123, 456 * * // Create Ref object from ref URI string * * ref = Ext.create('Rally.util.Ref', '/webservice/1.31/workspace/123'); // returns "workspace", 123, "1.31" * * // Create Ref object from WSAPI object with _ref property * * ref = Ext.create('Rally.util.Ref', {_ref: '/workspace/123'}); // returns "workspace", 123 * * // Create Ref object from UserStory model object * * var UserStory = Rally.data.ModelFactory.getModel('UserStory'); * * var userStory = new UserStory({ * _ref: "/hierarchicalrequirement/832687.js" * }); * ref = Ext.create('Rally.util.Ref', userStory); // returns "hierarchicalrequirement", 832687 */ Ext.define('Rally.util.Ref', { requires: [ 'Rally.data.Model', 'Rally.util.TypeInfo', 'Rally.util.RegularExpression' ], statics: { getCombinedRegularExpression: function() { if (!Rally.environment.narrowJsScriptingValley || (!this.combinedRegEx && Rally.environment.narrowJsScriptingValley)) { this.combinedRegEx = Rally.util.RegularExpression.combineRegularExpressions(Rally.util.RegularExpression.refRegularExpressions); } return this.combinedRegEx; }, /** * Determines if input is a valid reference uri. * * @param {String} uri the uri to parse * @return {Boolean} */ isRefUri: function(uri) { return Ext.isString(uri) && Rally.util.Ref.getCombinedRegularExpression().test(uri); }, /** * Determines if input is a valid reference uri and it has a valid object id. * * @param {String} uri the uri to parse * @return {Boolean} */ isRefUriWithValidObjectId: function(uri) { if (uri && this.isRefUri(uri)) { var oid = this.getRefObject(uri).getOid(); if (Ext.isNumber(oid)) { return parseInt(oid, 10) > 0; } // if the oid is not a number, it's probably a uuid // todo: validate uuid format return true; } return false; }, /** * Determines if input is a valid reference object. * * @param {Object} reference * @return {Boolean} True if the passed-in value is an instance of Rally.util.Ref */ isRefObject: function(reference) { return (Ext.isObject(reference) && (!Ext.isEmpty(reference.getOid)) && (!Ext.isEmpty(reference.getType))); }, getRefUri: function(input) { if (this.isRefUri(input)) { return input; } if (input && Ext.isObject(input)) { if (Ext.isFunction(input.get) && this.isRefUri(input.get('_ref'))) { return input.get('_ref'); } else if (this.isRefUri(input._ref)) { return input._ref; } } return null; }, /** * Parses reference data from input of varying types. * * @param {String/Rally.data.Model/Object} input Either a model object, a valid reference uri string or an object with * a _ref property that is a valid reference uri string. * @return {Rally.util.Ref} Newly constructed Rally.util.Ref object */ getRefObject: function(input) { var refUri = this.getRefUri(input); if (!this.isRefUri(refUri)) { throw Error('Reference [' + input + '] must be a valid WSAPI uri or a valid reference object.'); } return Ext.create('Rally.util.Ref', refUri); }, /** * Get the reference uri relative to the WSAPI endpoint. * * @param {Rally.util.Ref/Rally.data.Model/Object/String} reference Either a model object, a valid reference uri string or an object with * a _ref property that is a valid reference uri string. * @return {String} uri The relative uri of the reference object */ getRelativeUri: function(reference) { var refUri = this.getRefUri(reference), relativeUri = null; if (refUri) { relativeUri = Ext.create('Rally.util.Ref', refUri).getRelativeUri(); } else if (this.isRefObject(reference)) { relativeUri = this.getRelativeUriFromUriWithVersion(reference.getRelativeUriWithVersion()); } return relativeUri; }, /** * Get an absolute url for a reference * * @param {Rally.util.Ref/String} reference object with reference parameters * @return {String} url The absolute uri of the reference object */ getUrl: function(reference) { var uri = this.getUri(reference), origin = window.location.protocol + '//' + window.location.host; if (!uri.match(/^http/)) { uri = origin + uri; } return uri; }, getRelativeUriFromUriWithVersion: function(uri) { if (!uri) { return null; } var results = Rally.util.Ref.getCombinedRegularExpression().exec(uri); var scopingPart = results[1]; var typePart = results[2]; var oidPart = results[3]; var collectionPart = results[4]; if (scopingPart) { uri = "/" + scopingPart + "/" + typePart + "/" + oidPart; } else { uri = "/" + typePart + "/" + oidPart; } if (collectionPart) { uri += collectionPart; } return uri; }, addProjectParameterIfEditingAtProject: function(reference, url) { var refUri = reference.getRelativeUri(); var urlPartsArray = refUri.split('/'); if (urlPartsArray[1].toUpperCase() === 'PROJECT') {// || urlPartsArray[1].toUpperCase === 'WORKSPACE' url += '&scopeOid=' + urlPartsArray[2]; } return url; }, /** * Get the reference uri. * * @param {Object} reference object with reference parameters * @return {String} uri */ getUri: function(reference) { var version = ((Ext.isFunction(reference.getVersion) && reference.getVersion()) || Rally.environment.getServer().getWsapiVersion()); return Rally.environment.getServer().getWsapiUrl(version) + this.getRelativeUri(reference); }, /** * Get the ObjectID from a ref. * @param input a ref, like '/hierarchicalrequirement/123' * @return {Number|String} the ObjectID, if found. */ getOidFromRef: function(input) { var ref = this.getRefUri(input), objectId, oid; if (ref) { objectId = Rally.util.Ref.getCombinedRegularExpression().exec(ref)[3]; oid = UUID_FORMAT.test(objectId) || PERMISSION_FORMAT.test(objectId) ? objectId : parseInt(objectId, 10); return oid; } return null; }, getTypeFromRef: function(input) { var ref = this.getRefUri(input); ref = this.getRelativeUri(ref); if (ref) { var groups = Rally.util.Ref.getCombinedRegularExpression().exec(ref); if (groups[1] === 'portfolioitem') { return groups[1] + '/' + groups[2]; } else { return groups[2]; } } return null; }, getRefFromTypeAndOid: function(type, oid) { if (Ext.isString(type) && !isNaN(oid)) { return "/" + type.toLowerCase() + "/" + oid.toString(); } return null; } }, constructor: function(reference) { if (Ext.isObject(reference) && reference.oid && reference.type) { reference.relativeUriWithVersion = '/' + reference.type + '/' + reference.oid; this.reference = reference; } else { this.reference = this._parseUri(reference); } }, /** * Get the type of the record. * * @return {String} type */ getType: function() { return this.reference.type || null; }, /** * Get the OID of the record. * * @return {String} oid */ getOid: function() { return this.reference.oid || null; }, /* * Get the type def oid of the record, if it exists. * * @return {String} typeDefOid */ getTypeDefOid: function() { return this.reference.typeDefOid || null; }, getScopeType: function() { return this.reference.scopeType || null; }, getScopeOid: function() { return this.reference.scopeOid || null; }, /** * Get the version of the API used. * * @return {String} version */ getVersion: function() { return this.reference.version || 'x'; }, /** * @param {String} version The version of the ref */ setVersion: function (version) { this.reference.version = version; }, /** * Get the URI string without domain and webservice prefix, but including version. * @return {String} The URI string without domain and webservice prefix, but including version. */ getRelativeUriWithVersion: function() { return this.reference.relativeUriWithVersion || null; }, /** * Get the uri string that was parsed to create this object. * @return {String} The uri string that was parsed to create this object. */ getParsedUri: function() { return this.reference.parsedUri; }, /** * Get the reference uri relative to the WSAPI endpoint. * * @return {String} The reference uri relative to the WSAPI endpoint. */ getRelativeUri: function() { return this.self.getRelativeUri(this); }, /** * Get the reference uri. * * @return {String} The reference uri. */ getUri: function() { return this.self.getUri(this); }, getTypeInfo: function() { return Rally.util.TypeInfo.getTypeInfoByName(this.getType()); }, /** * @private * Get an object containing the component parts of a reference uri. * * @param {String} uri the uri to parse * @return {Object} reference object with reference details. */ _parseUri: function(inputUri) { var uri = inputUri; if (!this.self.isRefUri(uri)) { throw Error('Reference [' + uri + '] must be a valid WSAPI uri'); } uri = uri.replace(/^https?:\/\/[^\/]+?/, ''); uri = uri.replace(/^.*?webservice/, ''); if (!uri.match(/^\//)) { uri = '/' + uri; } var parts = uri.split('/'), length = parts.length, ref = { oid: this.self.getOidFromRef(uri), type: this._parseTypeFromUri(uri), relativeUriWithVersion: uri, parsedUri: inputUri }, versionCandidate = parts[1], server = Rally.environment.getServer(); if (EXPLICIT_VERSION_FORMAT.test(versionCandidate) || Ext.Array.contains(server.getWsapiVersionAliases(), versionCandidate)) { ref.version = versionCandidate; } else { ref.version = server.getWsapiVersion(); } if (length >= 4 && parts[length - 4].toUpperCase() === "TYPEDEFINITION") { ref.typeDefOid = parseInt(parts[length - 3], 10); } if (length >= 6) { var scopeType = parts[length - 6].toUpperCase(); if (scopeType === "PROJECT") { ref.scopeType = 'project'; } else if (scopeType === 'WORKSPACE') { ref.scopeType = 'workspace'; } ref.scopeOid = parseInt(parts[length - 5], 10); } return ref; }, _parseTypeFromUri: function(uri) { var relativeUri = this.self.getRelativeUriFromUriWithVersion(uri); var relativeUriParts = relativeUri.split("/"); if(this._isNestedType(relativeUriParts)) { return this._buildFullyQualifiedTypeName(relativeUriParts); } return this._getTypeFromUriParts(relativeUriParts); }, _buildFullyQualifiedTypeName: function(uriParts) { return uriParts[1] + "/" + uriParts[2]; }, _isNestedType: function(uriParts) { return uriParts.length === 4; }, _getTypeFromUriParts: function(uriParts) { return uriParts[uriParts.length - 2]; } }); })();