<commit_before>import {nullCommit} from '../commit';
import {nullBranch} from '../branch';

export const expectedDelegates = [];

/**
 * Map of registered subclasses to allow states to transition to one another without circular dependencies.
 * Subclasses of State should call `State.register` to add themselves here.
 */
const stateConstructors = new Map();

/**
 * Methods marked with this decorator on State should be delegated from a Repository to its current state. This will
 * be verified by a unit test in `repository.test.js`.
 */
function shouldDelegate(target, name, descriptor) {
  expectedDelegates.push(name);
  return descriptor;
}

/**
 * Base class for Repository states. Implements default "null" behavior.
 */
export default class State {
  constructor(repository) {
    this.repository = repository;
  }

  static register(Subclass) {
    stateConstructors.set(Subclass.name, Subclass);
  }

  // This state has just been entered. Perform any asynchronous initialization that needs to occur.
  start() {
    return Promise.resolve();
  }

  // State probe predicates ////////////////////////////////////////////////////////////////////////////////////////////
  // Allow external callers to identify which state a Repository is in if necessary.

  @shouldDelegate
  isLoading() {
    return false;
  }

  @shouldDelegate
  isEmpty() {
    return false;
  }

  @shouldDelegate
  isPresent() {
    return false;
  }

  @shouldDelegate
  isDestroyed() {
    return false;
  }

  // Lifecycle actions /////////////////////////////////////////////////////////////////////////////////////////////////
  // These generally default to rejecting a Promise with an error.

  @shouldDelegate
  init() {
    return unsupportedOperationPromise(this, 'init');
  }

  @shouldDelegate
  clone(remoteUrl) {
    return unsupportedOperationPromise(this, 'clone');
  }

  @shouldDelegate
  destroy() {
    return unsupportedOperation(this, 'destroy');
  }

  @shouldDelegate
  refresh() {
    // No-op
  }

  // Git operations ////////////////////////////////////////////////////////////////////////////////////////////////////
  // These default to rejecting a Promise with an error stating that the operation is not supported in the current
  // state.

  // Staging and unstaging

  @shouldDelegate
  stageFiles(paths) {
    return unsupportedOperationPromise(this, 'stageFiles');
  }

  @shouldDelegate
  unstageFiles(paths) {
    return unsupportedOperationPromise(this, 'unstageFiles');
  }

  @shouldDelegate
  stageFilesFromParentCommit(paths) {
    return unsupportedOperationPromise(this, 'stageFilesFromParentCommit');
  }

  @shouldDelegate
  applyPatchToIndex(patch) {
    return unsupportedOperationPromise(this, 'applyPatchToIndex');
  }

  @shouldDelegate
  applyPatchToWorkdir(patch) {
    return unsupportedOperationPromise(this, 'applyPatchToWorkdir');
  }

  // Committing

  @shouldDelegate
  commit(message, options) {
    return unsupportedOperationPromise(this, 'commit');
  }

  // Merging

  @shouldDelegate
  merge(branchName) {
    return unsupportedOperationPromise(this, 'merge');
  }

  @shouldDelegate
  abortMerge() {
    return unsupportedOperationPromise(this, 'abortMerge');
  }

  @shouldDelegate
  checkoutSide(side, paths) {
    return unsupportedOperationPromise(this, 'checkoutSide');
  }

  @shouldDelegate
  mergeFile(oursPath, commonBasePath, theirsPath, resultPath) {
    return unsupportedOperationPromise(this, 'mergeFile');
  }

  @shouldDelegate
  writeMergeConflictToIndex(filePath, commonBaseSha, oursSha, theirsSha) {
    return unsupportedOperationPromise(this, 'writeMergeConflictToIndex');
  }

  // Checkout

  @shouldDelegate
  checkout(revision, options = {}) {
    return unsupportedOperationPromise(this, 'checkout');
  }

  @shouldDelegate
  checkoutPathsAtRevision(paths, revision = 'HEAD') {
    return unsupportedOperationPromise(this, 'checkoutPathsAtRevision');
  }

  // Remote interactions

  @shouldDelegate
  fetch(branchName) {
    return unsupportedOperationPromise(this, 'fetch');
  }

  @shouldDelegate
  pull(branchName) {
    return unsupportedOperationPromise(this, 'pull');
  }

  @shouldDelegate
  push(branchName) {
    return unsupportedOperationPromise(this, 'push');
  }

  // Configuration

  @shouldDelegate
  setConfig(option, value, {replaceAll} = {}) {
    return unsupportedOperationPromise(this, 'setConfig');
  }

  // Direct blob interactions

  @shouldDelegate
  createBlob({filePath, stdin} = {}) {
    return unsupportedOperationPromise(this, 'createBlob');
  }

  @shouldDelegate
  expandBlobToFile(absFilePath, sha) {
    return unsupportedOperationPromise(this, 'expandBlobToFile');
  }

  // Discard history

  @shouldDelegate
  createDiscardHistoryBlob() {
    return unsupportedOperationPromise(this, 'createDiscardHistoryBlob');
  }

  @shouldDelegate
  updateDiscardHistory() {
    return unsupportedOperationPromise(this, 'updateDiscardHistory');
  }

  @shouldDelegate
  storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'storeBeforeAndAfterBlobs');
  }

  @shouldDelegate
  restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'restoreLastDiscardInTempFiles');
  }

  @shouldDelegate
  popDiscardHistory(partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'popDiscardHistory');
  }

  @shouldDelegate
  clearDiscardHistory(partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'clearDiscardHistory');
  }

  @shouldDelegate
  discardWorkDirChangesForPaths(paths) {
    return unsupportedOperationPromise(this, 'discardWorkDirChangesForPaths');
  }

  // Accessors /////////////////////////////////////////////////////////////////////////////////////////////////////////
  // When possible, these default to "empty" results when invoked in states that don't have information available, or
  // fail in a way that's consistent with the requested information not being found.

  // Index queries

  @shouldDelegate
  getStatusesForChangedFiles() {
    return Promise.resolve({
      stagedFiles: [],
      unstagedFiles: [],
      mergeConflictFiles: [],
    });
  }

  @shouldDelegate
  getStagedChangesSinceParentCommit() {
    return Promise.resolve([]);
  }

  @shouldDelegate
  isPartiallyStaged(fileName) {
    return Promise.resolve(false);
  }

  @shouldDelegate
  getFilePatchForPath(filePath, options = {}) {
    return Promise.resolve(null);
  }

  @shouldDelegate
  readFileFromIndex(filePath) {
    return Promise.reject(new Error(`fatal: Path ${filePath} does not exist (neither on disk nor in the index).`));
  }

  // Commit access

  @shouldDelegate
  getLastCommit() {
    return Promise.resolve(nullCommit);
  }

  // Branches

  @shouldDelegate
  getBranches() {
    return Promise.resolve([]);
  }

  @shouldDelegate
  getCurrentBranch() {
    return Promise.resolve(nullBranch);
  }

  // Merging and rebasing status

  @shouldDelegate
  isMerging() {
    return Promise.resolve(false);
  }

  @shouldDelegate
  isRebasing() {
    return Promise.resolve(false);
  }

  // Remotes

  @shouldDelegate
  getRemotes() {
    return Promise.resolve([]);
  }

  @shouldDelegate
  getAheadCount(branchName) {
    return Promise.resolve(null);
  }

  @shouldDelegate
  getBehindCount(branchName) {
    return Promise.resolve(null);
  }

  @shouldDelegate
  getConfig(option, {local} = {}) {
    return Promise.resolve(null);
  }

  // Direct blob access

  @shouldDelegate
  getBlobContents(sha) {
    return Promise.reject(new Error(`fatal: Not a valid object name ${sha}`));
  }

  // Discard history

  @shouldDelegate
  hasDiscardHistory(partialDiscardFilePath = null) {
    return false;
  }

  @shouldDelegate
  getDiscardHistory(partialDiscardFilePath = null) {
    return [];
  }

  @shouldDelegate
  getLastHistorySnapshots(partialDiscardFilePath = null) {
    return null;
  }

  // Internal //////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Non-delegated methods that provide subclasses with convenient access to Repository properties.

  git() {
    return this.repository.git;
  }

  workdir() {
    return this.repository.getWorkingDirectoryPath();
  }

  // Return a Promise that will resolve once the state transitions from Loading.
  getLoadPromise() {
    return this.repository.getLoadPromise();
  }

  getRemoteForBranch(branchName) {
    return this.repository.getRemoteForBranch(branchName);
  }

  saveDiscardHistory() {
    return this.repository.saveDiscardHistory();
  }

  // Initiate a transition to another state.
  transitionTo(stateName, ...payload) {
    const StateConstructor = stateConstructors.get(stateName);
    if (StateConstructor === undefined) {
      throw new Error(`Attempt to transition to unrecognized state ${stateName}`);
    }
    return this.repository.transition(this, StateConstructor, ...payload);
  }

  // Event broadcast

  didDestroy() {
    return this.repository.emitter.emit('did-destroy');
  }

  didUpdate() {
    return this.repository.emitter.emit('did-update');
  }

  // Parse a DiscardHistory payload from the SHA recorded in config.
  async loadHistoryPayload() {
    const historySha = await this.git().getConfig('atomGithub.historySha');
    if (!historySha) {
      return {};
    }

    let blob;
    try {
      blob = await this.getBlobContents(historySha);
    } catch (e) {
      if (/Not a valid object name/.test(e.stdErr)) {
        return {};
      }

      throw e;
    }

    try {
      return JSON.parse(blob);
    } catch (e) {
      return {};
    }
  }

  // Debugging assistance.

  toString() {
    return this.constructor.name;
  }
}

function unsupportedOperation(self, opName) {
  throw new Error(`${opName} is not available in ${self} state`);
}

function unsupportedOperationPromise(self, opName) {
  return Promise.reject(new Error(`${opName} is not available in ${self} state`));
}
<commit_msg>Use getBlobContents() from git strategy from loadHistoryPayload()<commit_after>import {nullCommit} from '../commit';
import {nullBranch} from '../branch';

export const expectedDelegates = [];

/**
 * Map of registered subclasses to allow states to transition to one another without circular dependencies.
 * Subclasses of State should call `State.register` to add themselves here.
 */
const stateConstructors = new Map();

/**
 * Methods marked with this decorator on State should be delegated from a Repository to its current state. This will
 * be verified by a unit test in `repository.test.js`.
 */
function shouldDelegate(target, name, descriptor) {
  expectedDelegates.push(name);
  return descriptor;
}

/**
 * Base class for Repository states. Implements default "null" behavior.
 */
export default class State {
  constructor(repository) {
    this.repository = repository;
  }

  static register(Subclass) {
    stateConstructors.set(Subclass.name, Subclass);
  }

  // This state has just been entered. Perform any asynchronous initialization that needs to occur.
  start() {
    return Promise.resolve();
  }

  // State probe predicates ////////////////////////////////////////////////////////////////////////////////////////////
  // Allow external callers to identify which state a Repository is in if necessary.

  @shouldDelegate
  isLoading() {
    return false;
  }

  @shouldDelegate
  isEmpty() {
    return false;
  }

  @shouldDelegate
  isPresent() {
    return false;
  }

  @shouldDelegate
  isDestroyed() {
    return false;
  }

  // Lifecycle actions /////////////////////////////////////////////////////////////////////////////////////////////////
  // These generally default to rejecting a Promise with an error.

  @shouldDelegate
  init() {
    return unsupportedOperationPromise(this, 'init');
  }

  @shouldDelegate
  clone(remoteUrl) {
    return unsupportedOperationPromise(this, 'clone');
  }

  @shouldDelegate
  destroy() {
    return unsupportedOperation(this, 'destroy');
  }

  @shouldDelegate
  refresh() {
    // No-op
  }

  // Git operations ////////////////////////////////////////////////////////////////////////////////////////////////////
  // These default to rejecting a Promise with an error stating that the operation is not supported in the current
  // state.

  // Staging and unstaging

  @shouldDelegate
  stageFiles(paths) {
    return unsupportedOperationPromise(this, 'stageFiles');
  }

  @shouldDelegate
  unstageFiles(paths) {
    return unsupportedOperationPromise(this, 'unstageFiles');
  }

  @shouldDelegate
  stageFilesFromParentCommit(paths) {
    return unsupportedOperationPromise(this, 'stageFilesFromParentCommit');
  }

  @shouldDelegate
  applyPatchToIndex(patch) {
    return unsupportedOperationPromise(this, 'applyPatchToIndex');
  }

  @shouldDelegate
  applyPatchToWorkdir(patch) {
    return unsupportedOperationPromise(this, 'applyPatchToWorkdir');
  }

  // Committing

  @shouldDelegate
  commit(message, options) {
    return unsupportedOperationPromise(this, 'commit');
  }

  // Merging

  @shouldDelegate
  merge(branchName) {
    return unsupportedOperationPromise(this, 'merge');
  }

  @shouldDelegate
  abortMerge() {
    return unsupportedOperationPromise(this, 'abortMerge');
  }

  @shouldDelegate
  checkoutSide(side, paths) {
    return unsupportedOperationPromise(this, 'checkoutSide');
  }

  @shouldDelegate
  mergeFile(oursPath, commonBasePath, theirsPath, resultPath) {
    return unsupportedOperationPromise(this, 'mergeFile');
  }

  @shouldDelegate
  writeMergeConflictToIndex(filePath, commonBaseSha, oursSha, theirsSha) {
    return unsupportedOperationPromise(this, 'writeMergeConflictToIndex');
  }

  // Checkout

  @shouldDelegate
  checkout(revision, options = {}) {
    return unsupportedOperationPromise(this, 'checkout');
  }

  @shouldDelegate
  checkoutPathsAtRevision(paths, revision = 'HEAD') {
    return unsupportedOperationPromise(this, 'checkoutPathsAtRevision');
  }

  // Remote interactions

  @shouldDelegate
  fetch(branchName) {
    return unsupportedOperationPromise(this, 'fetch');
  }

  @shouldDelegate
  pull(branchName) {
    return unsupportedOperationPromise(this, 'pull');
  }

  @shouldDelegate
  push(branchName) {
    return unsupportedOperationPromise(this, 'push');
  }

  // Configuration

  @shouldDelegate
  setConfig(option, value, {replaceAll} = {}) {
    return unsupportedOperationPromise(this, 'setConfig');
  }

  // Direct blob interactions

  @shouldDelegate
  createBlob({filePath, stdin} = {}) {
    return unsupportedOperationPromise(this, 'createBlob');
  }

  @shouldDelegate
  expandBlobToFile(absFilePath, sha) {
    return unsupportedOperationPromise(this, 'expandBlobToFile');
  }

  // Discard history

  @shouldDelegate
  createDiscardHistoryBlob() {
    return unsupportedOperationPromise(this, 'createDiscardHistoryBlob');
  }

  @shouldDelegate
  updateDiscardHistory() {
    return unsupportedOperationPromise(this, 'updateDiscardHistory');
  }

  @shouldDelegate
  storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'storeBeforeAndAfterBlobs');
  }

  @shouldDelegate
  restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'restoreLastDiscardInTempFiles');
  }

  @shouldDelegate
  popDiscardHistory(partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'popDiscardHistory');
  }

  @shouldDelegate
  clearDiscardHistory(partialDiscardFilePath = null) {
    return unsupportedOperationPromise(this, 'clearDiscardHistory');
  }

  @shouldDelegate
  discardWorkDirChangesForPaths(paths) {
    return unsupportedOperationPromise(this, 'discardWorkDirChangesForPaths');
  }

  // Accessors /////////////////////////////////////////////////////////////////////////////////////////////////////////
  // When possible, these default to "empty" results when invoked in states that don't have information available, or
  // fail in a way that's consistent with the requested information not being found.

  // Index queries

  @shouldDelegate
  getStatusesForChangedFiles() {
    return Promise.resolve({
      stagedFiles: [],
      unstagedFiles: [],
      mergeConflictFiles: [],
    });
  }

  @shouldDelegate
  getStagedChangesSinceParentCommit() {
    return Promise.resolve([]);
  }

  @shouldDelegate
  isPartiallyStaged(fileName) {
    return Promise.resolve(false);
  }

  @shouldDelegate
  getFilePatchForPath(filePath, options = {}) {
    return Promise.resolve(null);
  }

  @shouldDelegate
  readFileFromIndex(filePath) {
    return Promise.reject(new Error(`fatal: Path ${filePath} does not exist (neither on disk nor in the index).`));
  }

  // Commit access

  @shouldDelegate
  getLastCommit() {
    return Promise.resolve(nullCommit);
  }

  // Branches

  @shouldDelegate
  getBranches() {
    return Promise.resolve([]);
  }

  @shouldDelegate
  getCurrentBranch() {
    return Promise.resolve(nullBranch);
  }

  // Merging and rebasing status

  @shouldDelegate
  isMerging() {
    return Promise.resolve(false);
  }

  @shouldDelegate
  isRebasing() {
    return Promise.resolve(false);
  }

  // Remotes

  @shouldDelegate
  getRemotes() {
    return Promise.resolve([]);
  }

  @shouldDelegate
  getAheadCount(branchName) {
    return Promise.resolve(null);
  }

  @shouldDelegate
  getBehindCount(branchName) {
    return Promise.resolve(null);
  }

  @shouldDelegate
  getConfig(option, {local} = {}) {
    return Promise.resolve(null);
  }

  // Direct blob access

  @shouldDelegate
  getBlobContents(sha) {
    return Promise.reject(new Error(`fatal: Not a valid object name ${sha}`));
  }

  // Discard history

  @shouldDelegate
  hasDiscardHistory(partialDiscardFilePath = null) {
    return false;
  }

  @shouldDelegate
  getDiscardHistory(partialDiscardFilePath = null) {
    return [];
  }

  @shouldDelegate
  getLastHistorySnapshots(partialDiscardFilePath = null) {
    return null;
  }

  // Internal //////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Non-delegated methods that provide subclasses with convenient access to Repository properties.

  git() {
    return this.repository.git;
  }

  workdir() {
    return this.repository.getWorkingDirectoryPath();
  }

  // Return a Promise that will resolve once the state transitions from Loading.
  getLoadPromise() {
    return this.repository.getLoadPromise();
  }

  getRemoteForBranch(branchName) {
    return this.repository.getRemoteForBranch(branchName);
  }

  saveDiscardHistory() {
    return this.repository.saveDiscardHistory();
  }

  // Initiate a transition to another state.
  transitionTo(stateName, ...payload) {
    const StateConstructor = stateConstructors.get(stateName);
    if (StateConstructor === undefined) {
      throw new Error(`Attempt to transition to unrecognized state ${stateName}`);
    }
    return this.repository.transition(this, StateConstructor, ...payload);
  }

  // Event broadcast

  didDestroy() {
    return this.repository.emitter.emit('did-destroy');
  }

  didUpdate() {
    return this.repository.emitter.emit('did-update');
  }

  // Parse a DiscardHistory payload from the SHA recorded in config.
  async loadHistoryPayload() {
    const historySha = await this.git().getConfig('atomGithub.historySha');
    if (!historySha) {
      return {};
    }

    let blob;
    try {
      blob = await this.git().getBlobContents(historySha);
    } catch (e) {
      if (/Not a valid object name/.test(e.stdErr)) {
        return {};
      }

      throw e;
    }

    try {
      return JSON.parse(blob);
    } catch (e) {
      return {};
    }
  }

  // Debugging assistance.

  toString() {
    return this.constructor.name;
  }
}

function unsupportedOperation(self, opName) {
  throw new Error(`${opName} is not available in ${self} state`);
}

function unsupportedOperationPromise(self, opName) {
  return Promise.reject(new Error(`${opName} is not available in ${self} state`));
}
<|endoftext|>


<commit_before>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'sourceSelector', mw.KBaseComponent.extend({

		defaultConfig: {
			"parent": "controlsContainer",
			"order": 61,
			"displayImportance": 'low',
			"align": "right",
			"showTooltip": true,
			"switchOnResize": false,
			"simpleFormat": true,
			"iconClass": "icon-cog",
            "displayMode": "size", //'size' – displays frame size ( default ), 'bitrate' – displays the bitrate, 'sizebitrate' displays size followed by bitrate
            "hideSource": false,
			"title": gM( 'mwe-embedplayer-select_source' ),
			'smartContainer': 'qualitySettings',
			'smartContainerCloseEvent': 'SourceChange'
		},

		isDisabled: false,
		inUpdateLayout:false,
		selectSourceTitle: gM( 'mwe-embedplayer-select_source' ),
		switchSourceTitle: gM( 'mwe-embedplayer-switch_source' ),
        AutoTitle: gM( 'mwe-embedplayer-auto_source' ),
		saveBackgroundColor: null, // used to save background color upon disable and rotate and return it when enabled again to prevent rotating box around the icon when custom style is applied

        sourcesList: [],
        firstPlay:false,

		setup: function(){
			var _this = this;

			this.bind( 'playerReady sourcesReplaced', function(){
				_this.buildMenu();
			});

            this.bind( 'firstPlay', function(){
                _this.firstPlay = true;
            });

			this.bind( 'SourceChange', function(){
				var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
				var selectedId = selectedSrc.getAssetId();

				//if selected source is not part of the menu, show the source before it as the selected one
				//workaround when auto switch with kplayer occurred and the selected source is not part of the menu data provider
				if ( selectedSrc.skip ) {
					var sources = _this.getSources();
					for ( var i = 0; i< sources.length; i++ ) {
						//look for the closest flavor
						if ( selectedSrc.getSrc() == sources[i].getSrc() ) {
							if ( i == 0 && sources.length > 1 ) {
								selectedId = sources[i+1].getAssetId();
							} else {
								selectedId = sources[i-1].getAssetId();
							}
							break;
						}
					}
				}
				_this.getMenu().setActive({'key': 'id', 'val': selectedId});
			});

			this.bind( 'sourceSwitchingStarted', function(){
				_this.getComponent().find('button').addClass( 'in-progress-state' );
				_this.onDisable();
			});
			this.bind( 'sourceSwitchingEnd', function(newIndex){
				_this.getComponent().find('button').removeClass( 'in-progress-state' );
                _this.onEnable();
			});
            this.bind( 'onHideControlBar', function(){
                if ( _this.getMenu().isOpen() )
                    _this.getMenu().close();
            });
			this.bind( 'onChangeMedia', function(){
				_this.sourcesList = [];
			});

			this.bind( 'onDisableInterfaceComponents', function(e, arg ){
				_this.getMenu().close();
			});

			// Check for switch on resize option
			if( this.getConfig( 'switchOnResize' ) && !_this.embedPlayer.isLive() ){
				this.bind( 'resizeEvent', function(){
					// workaround to avoid the amount of 'updateLayout' events
					// !seeking will avoid getting current time equal to 0
					if ( !_this.inUpdateLayout && !_this.embedPlayer.seeking ){
						_this.inUpdateLayout = true;
						_this.updateLayoutTimout = setTimeout(function() {
							_this.inUpdateLayout = false;
						},1000);
						//if we're working with kplayer - mp4 can't be seeked - so disable this feature
						//this only effect native for now
						if (_this.embedPlayer.instanceOf === "Native" && !_this.embedPlayer.isInSequence() ) {
							// TODO add additional logic for "auto" where multiple bitrates
							// exist at the same resolution.
							var selectedSource = _this.embedPlayer.mediaElement.autoSelectSource(_this.embedPlayer.supportsURLTimeEncoding(), _this.embedPlayer.startTime, _this.embedPlayer.pauseTime);
							if ( selectedSource ) { // source was found
								_this.embedPlayer.switchSrc( selectedSource );
							}
						} else {
							mw.log( "sourceSelector - switchOnResize is ignored - Can't switch source since not using native player or during ad playback");
						}
					}
				});
			}

			this.embedPlayer.bindHelper("propertyChangedEvent", function(event, data){
				if ( data.plugin === _this.pluginName ){
					if ( data.property === "sources" ){
						_this.getMenu().$el.find("li a")[data.value].click();
					}
				}
			});
		},
		getSources: function(){
			return this.getPlayer().getSources();
		},

		buildMenu: function(){
			var _this = this;

			// Destroy old menu
			this.getMenu().destroy();

			var sources = this.getSources().slice(0);

			if( ! sources.length ){
				_this.log("Error with getting sources");
				return ;
			}

            //add Auto for addaptive bitrate streams
            if ( !this.handleAdaptiveBitrateAndContinue() )
                return;

            if (this.getConfig('hideSource')) {
                this.getPlayer().mediaElement.removeSourceFlavor(sources);
            }

			if( sources.length == 1 ){
				// no need to do building menu logic.
				this.addSourceToMenu( sources[0], _this.getSourceTitle(sources[0]) );
				return ;
			}
			// sort by height then bitrate:
			sources.sort(function(a,b){
				var hdiff = b.getHeight() - a.getHeight();
				if( hdiff != 0 ){
					return hdiff;
				}
				return b.getBitrate() - a.getBitrate();
			});

			// if simple format don't include more then two sources per size in menu
			if( _this.getConfig( 'simpleFormat' ) ){
				var prevSource = null;
				var twice = false;
				$.each( sources, function( sourceIndex, source ) {
					if( ! prevSource ){
						prevSource = source;
						return true;
					}
					if( source.getHeight() != 0
						&&
						( _this.getSourceSizeName( prevSource )
							==
							_this.getSourceSizeName( source ) )
						){
						//if the selected source has the same height, skip this source
						var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
						if ( selectedSrc
							&&
							!_this.isSourceSelected( source )
							&&
							!_this.isSourceSelected( prevSource )
							&&
							( _this.getSourceSizeName( source )
								==
								_this.getSourceSizeName( selectedSrc ) )
							){
							source.skip = true;
						}
						else if( twice ){
							// don't skip if this is the default source:
							if( !_this.isSourceSelected( source ) ){
								// skip this source
								source.skip = true;
							} else {
								source.skip = false;
							}
							prevSource = source;
							return true;
						}
						// set the first source as "HQ"
						prevSource.hq = true;
						twice = true;
					} else {
						twice = false;
					}
					// always update prevSource
					prevSource = source;
				});
			}
			var items = [];
			var itemLabels = [];
			var prevSource = null;
			$.each( sources, function( sourceIndex, source ) {
				if( source.skip ){
					return true;
				}
				// Output the player select code:
				var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() );
				for ( var i = 0; i < supportingPlayers.length ; i++ ) {
					if(
						(
							_this.getPlayer().selectedPlayer === undefined
								&&
								supportingPlayers[i].library == 'Native'
							)
							||
							(
								_this.getPlayer().selectedPlayer !== undefined
									&&
									supportingPlayers[i].library == _this.getPlayer().selectedPlayer.library
								)
						){
						_this.addSourceToMenu( source );
						var label = _this.getSourceTitle( source )
						if ($.inArray(label, itemLabels) === -1){
							itemLabels.push(label)
							items.push({'label':label, 'value':label});
						}
						if (_this.embedPlayer.isMobileSkin() && _this.isSourceSelected(source)){
							_this.getMenu().setActive(sourceIndex);
						}
					}
				}
			});
			// dispatch event to be used by a master plugin if defined
			this.getPlayer().triggerHelper("updatePropertyEvent",{"plugin": this.pluginName, "property": "sources", "items": items, "selectedItem": this.getMenu().$el.find('.active a').text()});

		},
		isSourceSelected: function( source ){
			var _this = this;
			return ( _this.getPlayer().mediaElement.selectedSource && source.getSrc()
				==
				_this.getPlayer().mediaElement.selectedSource.getSrc()
				);
		},
        handleAdaptiveBitrateAndContinue: function (){
            //Silverlight smoothStream
            if( ( this.getPlayer().streamerType === "smoothStream" ) ){
                this.addAutoToMenu();
                return true;
            }

            //HLS, HDS
            if (mw.isNativeApp()) {
            	this.sourcesList = [];
                this.addAutoToMenu();
                return true;
            }

            if ( this.getPlayer().streamerType != "http" && !this.getPlayer().isPlaying() ){
                if(this.getPlayer().streamerType !== "hls" && !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer('kplayer')){ //If flash disabled, player fallback to http progressive, but the streamerType might still be hdnetwork
                    return true;
                }
                this.addAutoToMenu();
                return false;
            }

            if( this.getPlayer().streamerType != "http" && this.firstPlay ){ //add and select Auto for adaptive bitrate
                this.addAutoToMenu();
                this.firstPlay = false;
            }
            return true;
        },
        addAutoToMenu: function (){
            var _this = this;
            this.getMenu().addItem({
                'label': _this.AutoTitle,
                'callback': function () {
                    _this.getPlayer().switchSrc(-1);
                },
               'active': true
            });
        },
		addSourceToMenu: function( source ){
			var _this = this;

            var sourceLabel = this.getSourceTitle( source );
            if( $.inArray(sourceLabel, this.sourcesList) == -1 ) {

                this.sourcesList.push(sourceLabel);

                this.getMenu().addItem({
                    'label': sourceLabel,
                    'attributes': {
                        'id': source.getAssetId()
                    },
                    'callback': function () {
	                    _this.getPlayer().triggerHelper("newSourceSelected", source.getAssetId());
                        _this.getPlayer().switchSrc(source);
                    },
                    'active': _this.isSourceSelected(source)
                });
            }
		},
        getSourceSizeName: function( source ){
			if( source.getHeight() == 0 ){
				return gM( 'mwe-embedplayer-audio_source' ) + this.getSourceTitleBitrate(source);
			} else if( source.getHeight() < 255 ){
				return '240P';
			} else if( source.getHeight() < 370 ){
				return '360P';
			} else if( source.getHeight() < 500 ){
				return '480P';
			} else if( source.getHeight() < 800 ){
				return '720P';
			} else {
				return '1080P';
			}
		},
		getSourceTitle: function( source ){
			// We should return "Auto" for Apple HLS
			if( source.getMIMEType() == 'application/vnd.apple.mpegurl' ) {
				return this.AutoTitle;
			}

            var title = '';
            switch( this.getConfig( 'displayMode' ) ){
                case 'size' :
                    title = this.getSourceTitleSize(source);
                    break;
                case 'bitrate' :
                    title = this.getSourceTitleBitrate(source);
                    break;
                case 'sizebitrate' :
                    title = this.getSourceTitleSizeBitrate(source);
                    break;
            }
            return title;
		},
        getSourceTitleSize: function( source ){
            var title = '';
            if( source.getHeight() ){
                title = this.getSourceSizeName( source );
                if( this.getConfig( 'displayMode' ) === 'size' && this.getConfig( 'simpleFormat' ) && source.hq ){
                    title += ' HQ';
                }
            } else { //fallback for a case we don't have a frame size (height) for the source (for example HLS source)
                title = this.getSourceTitleBitrate(source);
            }
            return title;
        },
        getSourceTitleBitrate: function( source ){
            var title = '';
            if ( source.getBitrate() ) {
                var bits = ( Math.round( source.getBitrate() / 1024 * 10 ) / 10 ) + '';
                if( bits[0] == '0' ){
                    bits = bits.substring(1);
                }
                title+= ' ' + bits + ' Mbs ';
            }
            return title;
        },
        getSourceTitleSizeBitrate: function( source ){
            var title = '';
            if ( source.getHeight() ){
                title = this.getSourceSizeName( source ) + ' ';
            }
			if ( source.getHeight() != 0 ) {
				title += this.getSourceTitleBitrate(source);
			}
            return title;
        },
		toggleMenu: function(){
			if ( this.isDisabled ) {
				return;
			}
			this.getMenu().toggle();
		},
		getComponent: function() {
			var _this = this;
			if( !this.$el ) {
				var $menu = $( '<ul />' );
				var $button = $( '<button />' )
					.addClass( 'btn icon-cog' )
					.attr('title', _this.selectSourceTitle)
					.click( function(e){
						_this.toggleMenu();
					});
				this.setAccessibility($button,_this.selectSourceTitle);
				this.$el = $( '<div />' )
					.addClass( 'dropup' + this.getCssClass() )
					.append( $button, $menu );
			}
			return this.$el;
		},
		getMenu: function(){
			if( !this.menu ) {
				this.menu = new mw.KMenu(this.getComponent().find('ul'), {
					tabIndex: this.getBtn().attr('tabindex')
				});
			}
			return this.menu;
		},
		getBtn: function(){
			return this.getComponent().find( 'button' );
		},
		onEnable: function(){
			this.isDisabled = false;
			this.updateTooltip( this.selectSourceTitle );
			this.getBtn().removeClass( 'disabled' );
			if (this.saveBackgroundColor){
				this.getComponent().find('button').attr('style', 'background-color: ' + this.saveBackgroundColor + ' !important');
			}
		},
		onDisable: function(){
			this.isDisabled = true;
			this.updateTooltip( this.switchSourceTitle );
			this.saveBackgroundColor = this.getComponent().find('button').css("background-color");
			this.getComponent().find('button').attr('style', 'background-color: null !important');
			this.getComponent().removeClass( 'open' );
			this.getBtn().addClass( 'disabled' );
		}
	}));

} )( window.mw, window.jQuery );
<commit_msg>parseInt<commit_after>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'sourceSelector', mw.KBaseComponent.extend({

		defaultConfig: {
			"parent": "controlsContainer",
			"order": 61,
			"displayImportance": 'low',
			"align": "right",
			"showTooltip": true,
			"switchOnResize": false,
			"simpleFormat": true,
			"iconClass": "icon-cog",
            "displayMode": "size", //'size' – displays frame size ( default ), 'bitrate' – displays the bitrate, 'sizebitrate' displays size followed by bitrate
            "hideSource": false,
			"title": gM( 'mwe-embedplayer-select_source' ),
			'smartContainer': 'qualitySettings',
			'smartContainerCloseEvent': 'SourceChange'
		},

		isDisabled: false,
		inUpdateLayout:false,
		selectSourceTitle: gM( 'mwe-embedplayer-select_source' ),
		switchSourceTitle: gM( 'mwe-embedplayer-switch_source' ),
        AutoTitle: gM( 'mwe-embedplayer-auto_source' ),
		saveBackgroundColor: null, // used to save background color upon disable and rotate and return it when enabled again to prevent rotating box around the icon when custom style is applied

        sourcesList: [],
        firstPlay:false,

		setup: function(){
			var _this = this;

			this.bind( 'playerReady sourcesReplaced', function(){
				_this.buildMenu();
			});

            this.bind( 'firstPlay', function(){
                _this.firstPlay = true;
            });

			this.bind( 'SourceChange', function(){
				var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
				var selectedId = selectedSrc.getAssetId();

				//if selected source is not part of the menu, show the source before it as the selected one
				//workaround when auto switch with kplayer occurred and the selected source is not part of the menu data provider
				if ( selectedSrc.skip ) {
					var sources = _this.getSources();
					for ( var i = 0; i< sources.length; i++ ) {
						//look for the closest flavor
						if ( selectedSrc.getSrc() == sources[i].getSrc() ) {
							if ( i == 0 && sources.length > 1 ) {
								selectedId = sources[i+1].getAssetId();
							} else {
								selectedId = sources[i-1].getAssetId();
							}
							break;
						}
					}
				}
				_this.getMenu().setActive({'key': 'id', 'val': selectedId});
			});

			this.bind( 'sourceSwitchingStarted', function(){
				_this.getComponent().find('button').addClass( 'in-progress-state' );
				_this.onDisable();
			});
			this.bind( 'sourceSwitchingEnd', function(newIndex){
				_this.getComponent().find('button').removeClass( 'in-progress-state' );
                _this.onEnable();
			});
            this.bind( 'onHideControlBar', function(){
                if ( _this.getMenu().isOpen() )
                    _this.getMenu().close();
            });
			this.bind( 'onChangeMedia', function(){
				_this.sourcesList = [];
			});

			this.bind( 'onDisableInterfaceComponents', function(e, arg ){
				_this.getMenu().close();
			});

			// Check for switch on resize option
			if( this.getConfig( 'switchOnResize' ) && !_this.embedPlayer.isLive() ){
				this.bind( 'resizeEvent', function(){
					// workaround to avoid the amount of 'updateLayout' events
					// !seeking will avoid getting current time equal to 0
					if ( !_this.inUpdateLayout && !_this.embedPlayer.seeking ){
						_this.inUpdateLayout = true;
						_this.updateLayoutTimout = setTimeout(function() {
							_this.inUpdateLayout = false;
						},1000);
						//if we're working with kplayer - mp4 can't be seeked - so disable this feature
						//this only effect native for now
						if (_this.embedPlayer.instanceOf === "Native" && !_this.embedPlayer.isInSequence() ) {
							// TODO add additional logic for "auto" where multiple bitrates
							// exist at the same resolution.
							var selectedSource = _this.embedPlayer.mediaElement.autoSelectSource(_this.embedPlayer.supportsURLTimeEncoding(), _this.embedPlayer.startTime, _this.embedPlayer.pauseTime);
							if ( selectedSource ) { // source was found
								_this.embedPlayer.switchSrc( selectedSource );
							}
						} else {
							mw.log( "sourceSelector - switchOnResize is ignored - Can't switch source since not using native player or during ad playback");
						}
					}
				});
			}

			this.embedPlayer.bindHelper("propertyChangedEvent", function(event, data){
				if ( data.plugin === _this.pluginName ){
					if ( data.property === "sources" ){
						_this.getMenu().$el.find("li a")[data.value].click();
					}
				}
			});
		},
		getSources: function(){
			return this.getPlayer().getSources();
		},

		buildMenu: function(){
			var _this = this;

			// Destroy old menu
			this.getMenu().destroy();

			var sources = this.getSources().slice(0);

			if( ! sources.length ){
				_this.log("Error with getting sources");
				return ;
			}

            //add Auto for addaptive bitrate streams
            if ( !this.handleAdaptiveBitrateAndContinue() )
                return;

            if (this.getConfig('hideSource')) {
                this.getPlayer().mediaElement.removeSourceFlavor(sources);
            }

			if( sources.length == 1 ){
				// no need to do building menu logic.
				this.addSourceToMenu( sources[0], _this.getSourceTitle(sources[0]) );
				return ;
			}
			// sort by height then bitrate:
			sources.sort(function(a,b){
				var hdiff = b.getHeight() - a.getHeight();
				if( hdiff != 0 ){
					return hdiff;
				}
				return b.getBitrate() - a.getBitrate();
			});

			// if simple format don't include more then two sources per size in menu
			if( _this.getConfig( 'simpleFormat' ) ){
				var prevSource = null;
				var twice = false;
				$.each( sources, function( sourceIndex, source ) {
					if( ! prevSource ){
						prevSource = source;
						return true;
					}
					if( source.getHeight() != 0
						&&
						( _this.getSourceSizeName( prevSource )
							==
							_this.getSourceSizeName( source ) )
						){
						//if the selected source has the same height, skip this source
						var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
						if ( selectedSrc
							&&
							!_this.isSourceSelected( source )
							&&
							!_this.isSourceSelected( prevSource )
							&&
							( _this.getSourceSizeName( source )
								==
								_this.getSourceSizeName( selectedSrc ) )
							){
							source.skip = true;
						}
						else if( twice ){
							// don't skip if this is the default source:
							if( !_this.isSourceSelected( source ) ){
								// skip this source
								source.skip = true;
							} else {
								source.skip = false;
							}
							prevSource = source;
							return true;
						}
						// set the first source as "HQ"
						prevSource.hq = true;
						twice = true;
					} else {
						twice = false;
					}
					// always update prevSource
					prevSource = source;
				});
			}
			var items = [];
			var itemLabels = [];
			var prevSource = null;
			$.each( sources, function( sourceIndex, source ) {
				if( source.skip ){
					return true;
				}
				// Output the player select code:
				var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() );
				for ( var i = 0; i < supportingPlayers.length ; i++ ) {
					if(
						(
							_this.getPlayer().selectedPlayer === undefined
								&&
								supportingPlayers[i].library == 'Native'
							)
							||
							(
								_this.getPlayer().selectedPlayer !== undefined
									&&
									supportingPlayers[i].library == _this.getPlayer().selectedPlayer.library
								)
						){
						_this.addSourceToMenu( source );
						var label = _this.getSourceTitle( source )
						if ($.inArray(label, itemLabels) === -1){
							itemLabels.push(label)
							items.push({'label':label, 'value':label});
						}
						if (_this.embedPlayer.isMobileSkin() && _this.isSourceSelected(source)){
							_this.getMenu().setActive(sourceIndex);
						}
					}
				}
			});
			// dispatch event to be used by a master plugin if defined
			this.getPlayer().triggerHelper("updatePropertyEvent",{"plugin": this.pluginName, "property": "sources", "items": items, "selectedItem": this.getMenu().$el.find('.active a').text()});

		},
		isSourceSelected: function( source ){
			var _this = this;
			return ( _this.getPlayer().mediaElement.selectedSource && source.getSrc()
				==
				_this.getPlayer().mediaElement.selectedSource.getSrc()
				);
		},
        handleAdaptiveBitrateAndContinue: function (){
            //Silverlight smoothStream
            if( ( this.getPlayer().streamerType === "smoothStream" ) ){
                this.addAutoToMenu();
                return true;
            }

            //HLS, HDS
            if (mw.isNativeApp()) {
            	this.sourcesList = [];
                this.addAutoToMenu();
                return true;
            }

            if ( this.getPlayer().streamerType != "http" && !this.getPlayer().isPlaying() ){
                if(this.getPlayer().streamerType !== "hls" && !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer('kplayer')){ //If flash disabled, player fallback to http progressive, but the streamerType might still be hdnetwork
                    return true;
                }
                this.addAutoToMenu();
                return false;
            }

            if( this.getPlayer().streamerType != "http" && this.firstPlay ){ //add and select Auto for adaptive bitrate
                this.addAutoToMenu();
                this.firstPlay = false;
            }
            return true;
        },
        addAutoToMenu: function (){
            var _this = this;
            this.getMenu().addItem({
                'label': _this.AutoTitle,
                'callback': function () {
                    _this.getPlayer().switchSrc(-1);
                },
               'active': true
            });
        },
		addSourceToMenu: function( source ){
			var _this = this;

            var sourceLabel = this.getSourceTitle( source );
            if( $.inArray(sourceLabel, this.sourcesList) == -1 ) {

                this.sourcesList.push(sourceLabel);

                this.getMenu().addItem({
                    'label': sourceLabel,
                    'attributes': {
                        'id': source.getAssetId()
                    },
                    'callback': function () {
	                    _this.getPlayer().triggerHelper("newSourceSelected", source.getAssetId());
                        _this.getPlayer().switchSrc(source);
                    },
                    'active': _this.isSourceSelected(source)
                });
            }
		},
        getSourceSizeName: function( source ){
			if( source.getHeight() == 0 ){
				return gM( 'mwe-embedplayer-audio_source' ) + this.getSourceTitleBitrate(source);
			} else if( source.getHeight() < 255 ){
				return '240P';
			} else if( source.getHeight() < 370 ){
				return '360P';
			} else if( source.getHeight() < 500 ){
				return '480P';
			} else if( source.getHeight() < 800 ){
				return '720P';
			} else {
				return '1080P';
			}
		},
		getSourceTitle: function( source ){
			// We should return "Auto" for Apple HLS
			if( source.getMIMEType() == 'application/vnd.apple.mpegurl' ) {
				return this.AutoTitle;
			}

            var title = '';
            switch( this.getConfig( 'displayMode' ) ){
                case 'size' :
                    title = this.getSourceTitleSize(source);
                    break;
                case 'bitrate' :
                    title = this.getSourceTitleBitrate(source);
                    break;
                case 'sizebitrate' :
                    title = this.getSourceTitleSizeBitrate(source);
                    break;
            }
            return title;
		},
        getSourceTitleSize: function( source ){
            var title = '';
            if( source.getHeight() ){
                title = this.getSourceSizeName( source );
                if( this.getConfig( 'displayMode' ) === 'size' && this.getConfig( 'simpleFormat' ) && source.hq ){
                    title += ' HQ';
                }
            } else { //fallback for a case we don't have a frame size (height) for the source (for example HLS source)
                title = this.getSourceTitleBitrate(source);
            }
            return title;
        },
        getSourceTitleBitrate: function( source ){
            var title = '';
            if ( source.getBitrate() ) {
                var bits = ( Math.round( source.getBitrate() / 1024 * 10 ) / 10 ) + '';
                if( bits[0] == '0' ){
                    bits = bits.substring(1);
                }
                title+= ' ' + bits + ' Mbs ';
            }
            return title;
        },
        getSourceTitleSizeBitrate: function( source ){
            var title = '';
            if ( source.getHeight() ){
                title = this.getSourceSizeName( source ) + ' ';
            }
			if ( parseInt(source.getHeight()) !== 0 ) {
				title += this.getSourceTitleBitrate(source);
			}
            return title;
        },
		toggleMenu: function(){
			if ( this.isDisabled ) {
				return;
			}
			this.getMenu().toggle();
		},
		getComponent: function() {
			var _this = this;
			if( !this.$el ) {
				var $menu = $( '<ul />' );
				var $button = $( '<button />' )
					.addClass( 'btn icon-cog' )
					.attr('title', _this.selectSourceTitle)
					.click( function(e){
						_this.toggleMenu();
					});
				this.setAccessibility($button,_this.selectSourceTitle);
				this.$el = $( '<div />' )
					.addClass( 'dropup' + this.getCssClass() )
					.append( $button, $menu );
			}
			return this.$el;
		},
		getMenu: function(){
			if( !this.menu ) {
				this.menu = new mw.KMenu(this.getComponent().find('ul'), {
					tabIndex: this.getBtn().attr('tabindex')
				});
			}
			return this.menu;
		},
		getBtn: function(){
			return this.getComponent().find( 'button' );
		},
		onEnable: function(){
			this.isDisabled = false;
			this.updateTooltip( this.selectSourceTitle );
			this.getBtn().removeClass( 'disabled' );
			if (this.saveBackgroundColor){
				this.getComponent().find('button').attr('style', 'background-color: ' + this.saveBackgroundColor + ' !important');
			}
		},
		onDisable: function(){
			this.isDisabled = true;
			this.updateTooltip( this.switchSourceTitle );
			this.saveBackgroundColor = this.getComponent().find('button').css("background-color");
			this.getComponent().find('button').attr('style', 'background-color: null !important');
			this.getComponent().removeClass( 'open' );
			this.getBtn().addClass( 'disabled' );
		}
	}));

} )( window.mw, window.jQuery );
<|endoftext|>


<commit_before>define([
  'angular',
  'lodash',
  'moment',
  'app/core/utils/datemath',
  'app/core/utils/kbn',
  './annotation_query',
],
function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
  'use strict';

  /** @ngInject */
  function CloudWatchDatasource(instanceSettings, $q, backendSrv, templateSrv) {
    this.type = 'cloudwatch';
    this.name = instanceSettings.name;
    this.supportMetrics = true;
    this.proxyUrl = instanceSettings.url;
    this.defaultRegion = instanceSettings.jsonData.defaultRegion;

    var self = this;
    this.query = function(options) {
      var start = self.convertToCloudWatchTime(options.range.from, false);
      var end = self.convertToCloudWatchTime(options.range.to, true);

      var queries = [];
      options = angular.copy(options);
      options.targets = this.expandTemplateVariable(options.targets, templateSrv);
      _.each(options.targets, function(target) {
        if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) {
          return;
        }

        var query = {};
        query.region = templateSrv.replace(target.region, options.scopedVars);
        query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
        query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
        query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
        query.statistics = target.statistics;

        var period = this._getPeriod(target, query, options, start, end);
        target.period = period;
        query.period = period;

        queries.push(query);
      }.bind(this));

      // No valid targets, return the empty result to save a round trip.
      if (_.isEmpty(queries)) {
        var d = $q.defer();
        d.resolve({ data: [] });
        return d.promise;
      }

      var allQueryPromise = _.map(queries, function(query) {
        return this.performTimeSeriesQuery(query, start, end);
      }.bind(this));

      return $q.all(allQueryPromise).then(function(allResponse) {
        var result = [];

        _.each(allResponse, function(response, index) {
          var metrics = transformMetricData(response, options.targets[index], options.scopedVars);
          result = result.concat(metrics);
        });

        return {data: result};
      });
    };

    this._getPeriod = function(target, query, options, start, end) {
      var period;
      var range = end - start;

      if (!target.period) {
        period = (query.namespace === 'AWS/EC2') ? 300 : 60;
      } else if (/^\d+$/.test(target.period)) {
        period = parseInt(target.period, 10);
      } else {
        period = kbn.interval_to_seconds(templateSrv.replace(target.period, options.scopedVars));
      }
      if (query.period < 60) {
        period = 60;
      }
      if (range / query.period >= 1440) {
        period = Math.ceil(range / 1440 / 60) * 60;
      }

      return period;
    };

    this.performTimeSeriesQuery = function(query, start, end) {
      return this.awsRequest({
        region: query.region,
        action: 'GetMetricStatistics',
        parameters:  {
          namespace: query.namespace,
          metricName: query.metricName,
          dimensions: query.dimensions,
          statistics: query.statistics,
          startTime: start,
          endTime: end,
          period: query.period
        }
      });
    };

    this.getRegions = function() {
      return this.awsRequest({action: '__GetRegions'});
    };

    this.getNamespaces = function() {
      return this.awsRequest({action: '__GetNamespaces'});
    };

    this.getMetrics = function(namespace, region) {
      return this.awsRequest({
        action: '__GetMetrics',
        region: region,
        parameters: {
          namespace: templateSrv.replace(namespace)
        }
      });
    };

    this.getDimensionKeys = function(namespace, region) {
      return this.awsRequest({
        action: '__GetDimensions',
        region: region,
        parameters: {
          namespace: templateSrv.replace(namespace)
        }
      });
    };

    this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
      var request = {
        region: templateSrv.replace(region),
        action: 'ListMetrics',
        parameters: {
          namespace: templateSrv.replace(namespace),
          metricName: templateSrv.replace(metricName),
          dimensions: this.convertDimensionFormat(filterDimensions, {}),
        }
      };

      return this.awsRequest(request).then(function(result) {
        return _.chain(result.Metrics)
        .map('Dimensions')
        .flatten()
        .filter(function(dimension) {
          return dimension !== null && dimension.Name === dimensionKey;
        })
        .map('Value')
        .uniq()
        .sortBy()
        .map(function(value) {
          return {value: value, text: value};
        }).value();
      });
    };

    this.performEC2DescribeInstances = function(region, filters, instanceIds) {
      return this.awsRequest({
        region: region,
        action: 'DescribeInstances',
        parameters: { filters: filters, instanceIds: instanceIds }
      });
    };

    this.metricFindQuery = function(query) {
      var region;
      var namespace;
      var metricName;

      var transformSuggestData = function(suggestData) {
        return _.map(suggestData, function(v) {
          return { text: v };
        });
      };

      var regionQuery = query.match(/^regions\(\)/);
      if (regionQuery) {
        return this.getRegions();
      }

      var namespaceQuery = query.match(/^namespaces\(\)/);
      if (namespaceQuery) {
        return this.getNamespaces();
      }

      var metricNameQuery = query.match(/^metrics\(([^\)]+?)(,\s?([^,]+?))?\)/);
      if (metricNameQuery) {
        return this.getMetrics(metricNameQuery[1], metricNameQuery[3]);
      }

      var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)(,\s?([^,]+?))?\)/);
      if (dimensionKeysQuery) {
        return this.getDimensionKeys(dimensionKeysQuery[1], dimensionKeysQuery[3]);
      }

      var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/);
      if (dimensionValuesQuery) {
        region = templateSrv.replace(dimensionValuesQuery[1]);
        namespace = templateSrv.replace(dimensionValuesQuery[2]);
        metricName = templateSrv.replace(dimensionValuesQuery[3]);
        var dimensionKey = templateSrv.replace(dimensionValuesQuery[4]);

        return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
      }

      var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
      if (ebsVolumeIdsQuery) {
        region = templateSrv.replace(ebsVolumeIdsQuery[1]);
        var instanceId = templateSrv.replace(ebsVolumeIdsQuery[2]);
        var instanceIds = [
          instanceId
        ];

        return this.performEC2DescribeInstances(region, [], instanceIds).then(function(result) {
          var volumeIds = _.map(result.Reservations[0].Instances[0].BlockDeviceMappings, function(mapping) {
            return mapping.Ebs.VolumeId;
          });

          return transformSuggestData(volumeIds);
        });
      }

      var ec2InstanceAttributeQuery = query.match(/^ec2_instance_attribute\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/);
      if (ec2InstanceAttributeQuery) {
        region = templateSrv.replace(ec2InstanceAttributeQuery[1]);
        var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3]));
        var filters = _.map(filterJson, function(values, name) {
          return {
            Name: name,
            Values: values
          };
        });
        var targetAttributeName = templateSrv.replace(ec2InstanceAttributeQuery[2]);

        return this.performEC2DescribeInstances(region, filters, null).then(function(result) {
          var attributes = _.chain(result.Reservations)
          .map(function(reservations) {
            return _.map(reservations.Instances, targetAttributeName);
          })
          .flatten().uniq().sortBy().value();
          return transformSuggestData(attributes);
        });
      }

      return $q.when([]);
    };

    this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
      return this.awsRequest({
        region: region,
        action: 'DescribeAlarms',
        parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
      });
    };

    this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
      return this.awsRequest({
        region: region,
        action: 'DescribeAlarmsForMetric',
        parameters: { namespace: namespace, metricName: metricName, dimensions: dimensions, statistic: statistic, period: period }
      });
    };

    this.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) {
      return this.awsRequest({
        region: region,
        action: 'DescribeAlarmHistory',
        parameters: { alarmName: alarmName, startDate: startDate, endDate: endDate }
      });
    };

    this.annotationQuery = function(options) {
      var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
      return annotationQuery.process(options.range.from, options.range.to);
    };

    this.testDatasource = function() {
      /* use billing metrics for test */
      var region = this.defaultRegion;
      var namespace = 'AWS/Billing';
      var metricName = 'EstimatedCharges';
      var dimensions = {};

      return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
        return { status: 'success', message: 'Data source is working', title: 'Success' };
      });
    };

    this.awsRequest = function(data) {
      var options = {
        method: 'POST',
        url: this.proxyUrl,
        data: data
      };

      return backendSrv.datasourceRequest(options).then(function(result) {
        return result.data;
      });
    };

    this.getDefaultRegion = function() {
      return this.defaultRegion;
    };

    function transformMetricData(md, options, scopedVars) {
      var aliasRegex = /\{\{(.+?)\}\}/g;
      var aliasPattern = options.alias || '{{metric}}_{{stat}}';
      var aliasData = {
        region: templateSrv.replace(options.region, scopedVars),
        namespace: templateSrv.replace(options.namespace, scopedVars),
        metric: templateSrv.replace(options.metricName, scopedVars),
      };

      var aliasDimensions = {};

      _.each(_.keys(options.dimensions), function(origKey) {
        var key = templateSrv.replace(origKey, scopedVars);
        var value = templateSrv.replace(options.dimensions[origKey], scopedVars);
        aliasDimensions[key] = value;
      });

      _.extend(aliasData, aliasDimensions);

      var periodMs = options.period * 1000;

      return _.map(options.statistics, function(stat) {
        var dps = [];
        var lastTimestamp = null;
        _.chain(md.Datapoints)
        .sortBy(function(dp) {
          return dp.Timestamp;
        })
        .each(function(dp) {
          var timestamp = new Date(dp.Timestamp).getTime();
          if (lastTimestamp && (timestamp - lastTimestamp) > periodMs) {
            dps.push([null, lastTimestamp + periodMs]);
          }
          lastTimestamp = timestamp;
          dps.push([dp[stat], timestamp]);
        })
        .value();

        aliasData.stat = stat;
        var seriesName = aliasPattern.replace(aliasRegex, function(match, g1) {
          if (aliasData[g1]) {
            return aliasData[g1];
          }
          return g1;
        });

        return {target: seriesName, datapoints: dps};
      });
    }

    this.getExpandedVariables = function(target, dimensionKey, variable) {
      /* if the all checkbox is marked we should add all values to the targets */
      var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
      return _.chain(variable.options)
      .filter(function(v) {
        if (allSelected) {
          return v.text !== 'All';
        } else {
          return v.selected;
        }
      })
      .map(function(v) {
        var t = angular.copy(target);
        t.dimensions[dimensionKey] = v.value;
        return t;
      }).value();
    };

    this.containsVariable = function (str, variableName) {
      return str.indexOf('$' + variableName) !== -1;
    };

    this.expandTemplateVariable = function(targets, templateSrv) {
      var self = this;
      return _.chain(targets)
      .map(function(target) {
        var dimensionKey = _.findKey(target.dimensions, function(v) {
          return templateSrv.variableExists(v);
        });

        if (dimensionKey) {
          var variable = _.find(templateSrv.variables, function(variable) {
            return self.containsVariable(target.dimensions[dimensionKey], variable.name);
          });
          return self.getExpandedVariables(target, dimensionKey, variable);
        } else {
          return [target];
        }
      }).flatten().value();
    };

    this.convertToCloudWatchTime = function(date, roundUp) {
      if (_.isString(date)) {
        date = dateMath.parse(date, roundUp);
      }
      return Math.round(date.valueOf() / 1000);
    };

    this.convertDimensionFormat = function(dimensions, scopedVars) {
      return _.map(dimensions, function(value, key) {
        return {
          Name: templateSrv.replace(key, scopedVars),
          Value: templateSrv.replace(value, scopedVars)
        };
      });
    };

  }

  return {
    CloudWatchDatasource: CloudWatchDatasource
  };
});
<commit_msg>Fix for cloudwatch datasource requesting too many datapoints (#6544)<commit_after>define([
  'angular',
  'lodash',
  'moment',
  'app/core/utils/datemath',
  'app/core/utils/kbn',
  './annotation_query',
],
function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
  'use strict';

  /** @ngInject */
  function CloudWatchDatasource(instanceSettings, $q, backendSrv, templateSrv) {
    this.type = 'cloudwatch';
    this.name = instanceSettings.name;
    this.supportMetrics = true;
    this.proxyUrl = instanceSettings.url;
    this.defaultRegion = instanceSettings.jsonData.defaultRegion;

    var self = this;
    this.query = function(options) {
      var start = self.convertToCloudWatchTime(options.range.from, false);
      var end = self.convertToCloudWatchTime(options.range.to, true);

      var queries = [];
      options = angular.copy(options);
      options.targets = this.expandTemplateVariable(options.targets, templateSrv);
      _.each(options.targets, function(target) {
        if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) {
          return;
        }

        var query = {};
        query.region = templateSrv.replace(target.region, options.scopedVars);
        query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
        query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
        query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
        query.statistics = target.statistics;

        var period = this._getPeriod(target, query, options, start, end);
        target.period = period;
        query.period = period;

        queries.push(query);
      }.bind(this));

      // No valid targets, return the empty result to save a round trip.
      if (_.isEmpty(queries)) {
        var d = $q.defer();
        d.resolve({ data: [] });
        return d.promise;
      }

      var allQueryPromise = _.map(queries, function(query) {
        return this.performTimeSeriesQuery(query, start, end);
      }.bind(this));

      return $q.all(allQueryPromise).then(function(allResponse) {
        var result = [];

        _.each(allResponse, function(response, index) {
          var metrics = transformMetricData(response, options.targets[index], options.scopedVars);
          result = result.concat(metrics);
        });

        return {data: result};
      });
    };

    this._getPeriod = function(target, query, options, start, end) {
      var period;
      var range = end - start;

      if (!target.period) {
        period = (query.namespace === 'AWS/EC2') ? 300 : 60;
      } else if (/^\d+$/.test(target.period)) {
        period = parseInt(target.period, 10);
      } else {
        period = kbn.interval_to_seconds(templateSrv.replace(target.period, options.scopedVars));
      }
      if (period < 60) {
        period = 60;
      }
      if (range / period >= 1440) {
        period = Math.ceil(range / 1440 / 60) * 60;
      }

      return period;
    };

    this.performTimeSeriesQuery = function(query, start, end) {
      return this.awsRequest({
        region: query.region,
        action: 'GetMetricStatistics',
        parameters:  {
          namespace: query.namespace,
          metricName: query.metricName,
          dimensions: query.dimensions,
          statistics: query.statistics,
          startTime: start,
          endTime: end,
          period: query.period
        }
      });
    };

    this.getRegions = function() {
      return this.awsRequest({action: '__GetRegions'});
    };

    this.getNamespaces = function() {
      return this.awsRequest({action: '__GetNamespaces'});
    };

    this.getMetrics = function(namespace, region) {
      return this.awsRequest({
        action: '__GetMetrics',
        region: region,
        parameters: {
          namespace: templateSrv.replace(namespace)
        }
      });
    };

    this.getDimensionKeys = function(namespace, region) {
      return this.awsRequest({
        action: '__GetDimensions',
        region: region,
        parameters: {
          namespace: templateSrv.replace(namespace)
        }
      });
    };

    this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
      var request = {
        region: templateSrv.replace(region),
        action: 'ListMetrics',
        parameters: {
          namespace: templateSrv.replace(namespace),
          metricName: templateSrv.replace(metricName),
          dimensions: this.convertDimensionFormat(filterDimensions, {}),
        }
      };

      return this.awsRequest(request).then(function(result) {
        return _.chain(result.Metrics)
        .map('Dimensions')
        .flatten()
        .filter(function(dimension) {
          return dimension !== null && dimension.Name === dimensionKey;
        })
        .map('Value')
        .uniq()
        .sortBy()
        .map(function(value) {
          return {value: value, text: value};
        }).value();
      });
    };

    this.performEC2DescribeInstances = function(region, filters, instanceIds) {
      return this.awsRequest({
        region: region,
        action: 'DescribeInstances',
        parameters: { filters: filters, instanceIds: instanceIds }
      });
    };

    this.metricFindQuery = function(query) {
      var region;
      var namespace;
      var metricName;

      var transformSuggestData = function(suggestData) {
        return _.map(suggestData, function(v) {
          return { text: v };
        });
      };

      var regionQuery = query.match(/^regions\(\)/);
      if (regionQuery) {
        return this.getRegions();
      }

      var namespaceQuery = query.match(/^namespaces\(\)/);
      if (namespaceQuery) {
        return this.getNamespaces();
      }

      var metricNameQuery = query.match(/^metrics\(([^\)]+?)(,\s?([^,]+?))?\)/);
      if (metricNameQuery) {
        return this.getMetrics(metricNameQuery[1], metricNameQuery[3]);
      }

      var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)(,\s?([^,]+?))?\)/);
      if (dimensionKeysQuery) {
        return this.getDimensionKeys(dimensionKeysQuery[1], dimensionKeysQuery[3]);
      }

      var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/);
      if (dimensionValuesQuery) {
        region = templateSrv.replace(dimensionValuesQuery[1]);
        namespace = templateSrv.replace(dimensionValuesQuery[2]);
        metricName = templateSrv.replace(dimensionValuesQuery[3]);
        var dimensionKey = templateSrv.replace(dimensionValuesQuery[4]);

        return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
      }

      var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
      if (ebsVolumeIdsQuery) {
        region = templateSrv.replace(ebsVolumeIdsQuery[1]);
        var instanceId = templateSrv.replace(ebsVolumeIdsQuery[2]);
        var instanceIds = [
          instanceId
        ];

        return this.performEC2DescribeInstances(region, [], instanceIds).then(function(result) {
          var volumeIds = _.map(result.Reservations[0].Instances[0].BlockDeviceMappings, function(mapping) {
            return mapping.Ebs.VolumeId;
          });

          return transformSuggestData(volumeIds);
        });
      }

      var ec2InstanceAttributeQuery = query.match(/^ec2_instance_attribute\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/);
      if (ec2InstanceAttributeQuery) {
        region = templateSrv.replace(ec2InstanceAttributeQuery[1]);
        var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3]));
        var filters = _.map(filterJson, function(values, name) {
          return {
            Name: name,
            Values: values
          };
        });
        var targetAttributeName = templateSrv.replace(ec2InstanceAttributeQuery[2]);

        return this.performEC2DescribeInstances(region, filters, null).then(function(result) {
          var attributes = _.chain(result.Reservations)
          .map(function(reservations) {
            return _.map(reservations.Instances, targetAttributeName);
          })
          .flatten().uniq().sortBy().value();
          return transformSuggestData(attributes);
        });
      }

      return $q.when([]);
    };

    this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
      return this.awsRequest({
        region: region,
        action: 'DescribeAlarms',
        parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
      });
    };

    this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
      return this.awsRequest({
        region: region,
        action: 'DescribeAlarmsForMetric',
        parameters: { namespace: namespace, metricName: metricName, dimensions: dimensions, statistic: statistic, period: period }
      });
    };

    this.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) {
      return this.awsRequest({
        region: region,
        action: 'DescribeAlarmHistory',
        parameters: { alarmName: alarmName, startDate: startDate, endDate: endDate }
      });
    };

    this.annotationQuery = function(options) {
      var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
      return annotationQuery.process(options.range.from, options.range.to);
    };

    this.testDatasource = function() {
      /* use billing metrics for test */
      var region = this.defaultRegion;
      var namespace = 'AWS/Billing';
      var metricName = 'EstimatedCharges';
      var dimensions = {};

      return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
        return { status: 'success', message: 'Data source is working', title: 'Success' };
      });
    };

    this.awsRequest = function(data) {
      var options = {
        method: 'POST',
        url: this.proxyUrl,
        data: data
      };

      return backendSrv.datasourceRequest(options).then(function(result) {
        return result.data;
      });
    };

    this.getDefaultRegion = function() {
      return this.defaultRegion;
    };

    function transformMetricData(md, options, scopedVars) {
      var aliasRegex = /\{\{(.+?)\}\}/g;
      var aliasPattern = options.alias || '{{metric}}_{{stat}}';
      var aliasData = {
        region: templateSrv.replace(options.region, scopedVars),
        namespace: templateSrv.replace(options.namespace, scopedVars),
        metric: templateSrv.replace(options.metricName, scopedVars),
      };

      var aliasDimensions = {};

      _.each(_.keys(options.dimensions), function(origKey) {
        var key = templateSrv.replace(origKey, scopedVars);
        var value = templateSrv.replace(options.dimensions[origKey], scopedVars);
        aliasDimensions[key] = value;
      });

      _.extend(aliasData, aliasDimensions);

      var periodMs = options.period * 1000;

      return _.map(options.statistics, function(stat) {
        var dps = [];
        var lastTimestamp = null;
        _.chain(md.Datapoints)
        .sortBy(function(dp) {
          return dp.Timestamp;
        })
        .each(function(dp) {
          var timestamp = new Date(dp.Timestamp).getTime();
          if (lastTimestamp && (timestamp - lastTimestamp) > periodMs) {
            dps.push([null, lastTimestamp + periodMs]);
          }
          lastTimestamp = timestamp;
          dps.push([dp[stat], timestamp]);
        })
        .value();

        aliasData.stat = stat;
        var seriesName = aliasPattern.replace(aliasRegex, function(match, g1) {
          if (aliasData[g1]) {
            return aliasData[g1];
          }
          return g1;
        });

        return {target: seriesName, datapoints: dps};
      });
    }

    this.getExpandedVariables = function(target, dimensionKey, variable) {
      /* if the all checkbox is marked we should add all values to the targets */
      var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
      return _.chain(variable.options)
      .filter(function(v) {
        if (allSelected) {
          return v.text !== 'All';
        } else {
          return v.selected;
        }
      })
      .map(function(v) {
        var t = angular.copy(target);
        t.dimensions[dimensionKey] = v.value;
        return t;
      }).value();
    };

    this.containsVariable = function (str, variableName) {
      return str.indexOf('$' + variableName) !== -1;
    };

    this.expandTemplateVariable = function(targets, templateSrv) {
      var self = this;
      return _.chain(targets)
      .map(function(target) {
        var dimensionKey = _.findKey(target.dimensions, function(v) {
          return templateSrv.variableExists(v);
        });

        if (dimensionKey) {
          var variable = _.find(templateSrv.variables, function(variable) {
            return self.containsVariable(target.dimensions[dimensionKey], variable.name);
          });
          return self.getExpandedVariables(target, dimensionKey, variable);
        } else {
          return [target];
        }
      }).flatten().value();
    };

    this.convertToCloudWatchTime = function(date, roundUp) {
      if (_.isString(date)) {
        date = dateMath.parse(date, roundUp);
      }
      return Math.round(date.valueOf() / 1000);
    };

    this.convertDimensionFormat = function(dimensions, scopedVars) {
      return _.map(dimensions, function(value, key) {
        return {
          Name: templateSrv.replace(key, scopedVars),
          Value: templateSrv.replace(value, scopedVars)
        };
      });
    };

  }

  return {
    CloudWatchDatasource: CloudWatchDatasource
  };
});
<|endoftext|>


<commit_before>import uuid from 'uuid';
import { nonenumerable } from 'core-decorators';

function fnBody(type) {
  return Function.prototype.toString.call(type).replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, '');
}

function isClass(type) {
  return (
    /^class[\s{]/.test(Function.prototype.toString.call(type)) ||
    (/^.*classCallCheck\(/.test(fnBody(type))) ||
    (/^.*classCallCheck.\.default\)/.test(fnBody(type)))
  );
}

export default class Model {
  static _primaryKey = 'generateUuid';
  static _requiredFields = [];
  static _messageRequiredField = 'This field is required';
  static _preFieldLabel = '';

  @nonenumerable
  generateUuid = '';

  @nonenumerable
  _options = '';

  constructor(obj, options = {}) {
    this._options = options;
    this._validateOptions();

    if (obj && obj.constructor === Object) {
      const unserializeObj = this._unserialize(obj);
      const finalObject = this._checkTypeProperties(unserializeObj);
      this._assignObject(finalObject);
    } else {
      throw new Error(
          'First argument passed to constructor is invalid, ' +
          'expect object(key,value) or array'
      );
    }
  }

  getPrimaryKey() {
    return this.constructor._primaryKey;
  }

  getKey() {
    return this[this.getPrimaryKey()];
  }

  checkRequiredFields() {
    for (const field of this.constructor._requiredFields) {
      if (!this[field] && typeof this[field] !== 'boolean') {
        return false;
      }
    }
    return true;
  }

  validateRequiredFields() {
    const errors = [];
    for (const field of this.constructor._requiredFields) {
      if (!this[field] && typeof this[field] !== 'boolean') {
        errors.push({
          field: this.constructor._preFieldLabel + field,
          message: this.constructor._messageRequiredField,
        });
      }
    }
    return errors;
  }

  isValidRequiredField(fieldName) {
    return this._notExistFieldAndNotRequired(fieldName) || this._existsAndIsRequired(fieldName);
  }

  _notExistFieldAndNotRequired(fieldName) {
    return typeof this[fieldName] !== 'boolean' && !this[fieldName] && !this._existInRequiredFields(fieldName);
  }

  _existsAndIsRequired(fieldName) {
    return this._existInRequiredFields(fieldName) && !!this[fieldName] && !this._isStringAndEmpty();
  }

  _isStringAndEmpty(fieldName) {
    return (typeof this[fieldName] === 'string' && this[fieldName] === '');
  }

  _existInRequiredFields(fieldName) {
    const existeRequired = this.constructor._requiredFields.includes(fieldName);
    // console.log('existeRequired', existeRequired);

    return existeRequired;
  }

  _unserialize(obj) {
    const { unserializers } = this._options;

    let unserializeObject = obj;
    if (unserializers) {
      for (const unserialize of unserializers) {
        unserializeObject = unserialize(unserializeObject);
      }
    }
    return unserializeObject;
  }

  _checkTypeProperties(obj) {
    const _types = this.constructor._types;
    if (_types) {
      const typesKeys = Object.keys(_types);
      for (const typeKey of typesKeys) {
        let FinalType = _types[typeKey];

        if (!isClass(FinalType)) {
          FinalType = FinalType(obj);
        }

        if (obj[typeKey] && obj[typeKey].constructor !== FinalType) {
          obj[typeKey] = // eslint-disable-line no-param-reassign
            new FinalType(obj[typeKey], this._options);
        }
      }
    }
    return obj;
  }

  _assignObject(finalObject) {
    Object.assign(this, finalObject);
    if (this.getPrimaryKey() === 'generateUuid') {
      this._generateUuid();
    }
    if (!this.getKey()) {
      throw new Error('The value for primary key is not defined');
    }
  }

  _generateUuid() {
    this.generateUuid = uuid.v1();
  }

  _validateOptions() {
    if (this._options.constructor !== Object) {
      throw new Error('options parameters has to be an object');
    }

    if (this._options.unserializers && !Array.isArray(this._options.unserializers)) {
      throw new Error('Unserializer has to be an array');
    }
  }
}
<commit_msg>Fix type<commit_after>import uuid from 'uuid';
import { nonenumerable } from 'core-decorators';

function fnBody(type) {
  return Function.prototype.toString.call(type).replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, '');
}

function isClass(type) {
  return (
    /^class[\s{]/.test(Function.prototype.toString.call(type)) ||
    (/^.*classCallCheck\(/.test(fnBody(type))) ||
    (/^.*classCallCheck.\.default\)/.test(fnBody(type)))
  );
}

export default class Model {
  static _primaryKey = 'generateUuid';
  static _requiredFields = [];
  static _messageRequiredField = 'This field is required';
  static _preFieldLabel = '';

  @nonenumerable
  generateUuid = '';

  @nonenumerable
  _options = '';

  constructor(obj, options = {}) {
    this._options = options;
    this._validateOptions();

    if (obj && obj.constructor === Object) {
      const unserializeObj = this._unserialize(obj);
      const finalObject = this._checkTypeProperties(unserializeObj);
      this._assignObject(finalObject);
    } else {
      throw new Error(
          'First argument passed to constructor is invalid, ' +
          'expect object(key,value) or array'
      );
    }
  }

  getPrimaryKey() {
    return this.constructor._primaryKey;
  }

  getKey() {
    return this[this.getPrimaryKey()];
  }

  checkRequiredFields() {
    for (const field of this.constructor._requiredFields) {
      if (!this[field] && typeof this[field] !== 'boolean') {
        return false;
      }
    }
    return true;
  }

  validateRequiredFields() {
    const errors = [];
    for (const field of this.constructor._requiredFields) {
      if (!this[field] && typeof this[field] !== 'boolean') {
        errors.push({
          field: this.constructor._preFieldLabel + field,
          message: this.constructor._messageRequiredField,
        });
      }
    }
    return errors;
  }

  isValidRequiredField(fieldName) {
    return this._notExistFieldAndNotRequired(fieldName) || this._existsAndIsRequired(fieldName);
  }

  _notExistFieldAndNotRequired(fieldName) {
    return typeof this[fieldName] !== 'boolean' && !this[fieldName] && !this._existInRequiredFields(fieldName);
  }

  _existsAndIsRequired(fieldName) {
    return this._existInRequiredFields(fieldName) && !!this[fieldName] && !this._isStringAndEmpty();
  }

  _isStringAndEmpty(fieldName) {
    return (typeof this[fieldName] === 'string' && this[fieldName] === '');
  }

  _existInRequiredFields(fieldName) {
    const existRequired = this.constructor._requiredFields.includes(fieldName);

    return existRequired;
  }

  _unserialize(obj) {
    const { unserializers } = this._options;

    let unserializeObject = obj;
    if (unserializers) {
      for (const unserialize of unserializers) {
        unserializeObject = unserialize(unserializeObject);
      }
    }
    return unserializeObject;
  }

  _checkTypeProperties(obj) {
    const _types = this.constructor._types;
    if (_types) {
      const typesKeys = Object.keys(_types);
      for (const typeKey of typesKeys) {
        let FinalType = _types[typeKey];

        if (!isClass(FinalType)) {
          FinalType = FinalType(obj);
        }

        if (obj[typeKey] && obj[typeKey].constructor !== FinalType) {
          obj[typeKey] = // eslint-disable-line no-param-reassign
            new FinalType(obj[typeKey], this._options);
        }
      }
    }
    return obj;
  }

  _assignObject(finalObject) {
    Object.assign(this, finalObject);
    if (this.getPrimaryKey() === 'generateUuid') {
      this._generateUuid();
    }
    if (!this.getKey()) {
      throw new Error('The value for primary key is not defined');
    }
  }

  _generateUuid() {
    this.generateUuid = uuid.v1();
  }

  _validateOptions() {
    if (this._options.constructor !== Object) {
      throw new Error('options parameters has to be an object');
    }

    if (this._options.unserializers && !Array.isArray(this._options.unserializers)) {
      throw new Error('Unserializer has to be an array');
    }
  }
}
<|endoftext|>