<commit_before>Polymer({
  is: 'arc-request-panel',

  properties: {
    /**
     * Current route params.
     */
    routeParams: Object,
    /**
     * Set by parent element to determine that this view is opened.
     */
    opened: Boolean,
    /**
     * Set by the `chrome-xhr-request` element. True if the proxy extension
     * is installed an connected to the application.
     */
    hasProxyInstalled: Boolean,
    /**
     * Current request object.
     */
    request: Object,
    /**
     * Keeps temp request data restored from the datastore / local store
     * to sync state between different stores and the UI.
     */
    proxyRequest: Object,
    /**
     * If set the panel will use the XHR proxy to send the request
     */
    useXhr: Boolean,
    // Currently selected project.
    projectId: String,
    // True to display cookie exchange instalkl banner.
    cookieBanner: Boolean
  },

  listeners: {
    'transport-request': '_onTransportRequested',
    'abort-request': '_abortHandler',
    'request-clear-state': '_clear'
  },

  observers: [
    '_routeChnaged(routeParams, opened)',
    '_openedChanged(opened)',
    '_proxyRequestChanged(proxyRequest.*)',
    '_requestChanged(request.*)',
    '_projectIdChanged(projectId)'
  ],

  attached: function() {
    this.listen(window, 'report-response', '_reportRequestHandler');
  },

  detached: function() {
    this.unlisten(window, 'report-response', '_reportRequestHandler');
  },

  _clear: function() {
    this.cookieBanner = false;
  },

  _routeChnaged: function(route, opened) {
    if (!opened || !route || !route.type) {
      return;
    }
    switch (route.type) {
      case 'saved':
      case 'history':
        let id = decodeURIComponent(this.routeParams.savedId || this.routeParams.historyId);
        this._restoreRequestData(id, route.type);
        break;
      case 'current':
      case 'drive':
        return;
      default:
        this.restoreLatest();
        break;
    }
  },

  _openedChanged: function(opened) {
    if (opened) {
      this._requestFeatures();
    } else {
      this._releaseFeatures();
    }
  },

  _requestFeatures: function() {
    this.fire('request-toolbar-features', {
      features: ['clearAll', 'save', 'xhrtoggle']
    }, {
      cancelable: true
    });
  },

  _releaseFeatures: function() {
    this.fire('release-toolbar-features', {}, {
      cancelable: true
    });
  },

  _onTransportRequested: function(e) {
    if (this.cookieBanner) {
      this.cookieBanner = false;
    }
    this.fire('url-history-store', {
      value: this.request.url
    }, {
      cancelable: true,
      composed: true
    });
    var panel;
    if (this.useXhr) {
      panel = this.$$('chrome-xhr-request');
    } else {
      panel = this.$$('chrome-socket-request');
    }
    panel.send(e.detail)
    .catch(cause => {
      console.error(cause);
    });
  },
  /**
   * Aborts the request.
   */
  _abortHandler: function() {
    var panel;
    if (this.useXhr) {
      panel = this.$$('chrome-xhr-request');
    } else {
      panel = this.$$('chrome-socket-request');
    }
    panel.abort();
    this.fire('url-history-store', {
      value: this.request.url
    }, {
      cancelable: true,
      composed: true
    });
  },
  /**
   * Handler for the XHR change event
   */
  _xhrChanged: function(e) {
    var requested = e.detail.value;
    e.preventDefault();
    e.stopPropagation();

    if (!this.hasProxyInstalled && requested) {
      this.$$('install-proxy-dialog').opened = true;
      return;
    }
    this.useXhr = requested;
    this.fire('send-analytics', {
      type: 'event',
      category: 'Request',
      action: 'Use XHR',
      label: requested + '',
    });
  },
  /**
   * Clears the request state
   */
  clearRequest: function(e) {
    this.set('request', {
      method: 'GET'
    });
    this.projectId = undefined;
    if (e && e.preventDefault) {
      e.preventDefault();
      e.stopPropagation();
    }
  },
  /**
   * Sends the request from the shortcut manager.
   * TODO: It uses private APIs and it shouldn't.
   */
  sendRequest: function() {
    var panel = this.$$('request-panel');
    var editor = panel.$$('request-editor');
    editor._sendRequest();
  },
  /**
   * Restores saved in the datastore request data.
   * @param {String} id ID of the record
   * @param {String} type Type of the stored data. Can be `saved`, `history` or
   * `drive`
   */
  _restoreRequestData: function(id, type) {
    type = type || 'saved';
    var eventType;
    switch (type) {
      case 'saved':
        eventType = 'saved-requests';
        break;
      case 'history':
        eventType = 'history-requests';
        break;
      case 'drive':
        eventType = 'saved-requests';
        break;
      default:
        this.fire('app-log', {
          'message': `${type} is not a type of a request.`,
          'level': 'error'
        });
        console.error('Can\'t restore request data. Type is unknown.', type);
        this.fire('send-analytics', {
          type: 'exception',
          description: 'Can\'t restore request of a type: ' + type + ' (ArcRequestPanel)',
          fatal: true
        });
        this.$.typeMissingToast.opened = true;
        return;
    }

    var event = this.fire('request-object-read', {
      id: id,
      type: eventType
    }, {
      cancelable: true
    });

    return event.detail.result
    .then(result => {
      result.type = type;
      this.set('proxyRequest', result);
    })
    .catch((e) => {
      this.fire('app-log', {
        'message': e,
        'level': 'error'
      });
      console.error('Can\'t restore request data', e);
      this.$.missingRequestToast.opened = true;
    });
  },
  /**
   * Sets the `request` object when a proxy change.
   */
  _proxyRequestChanged: function(record) {
    if (!record || !record.base) {
      return;
    }
    var data = record.base;
    var base = {
      _id: data._id,
      type: data.type,
      headers: data.headers,
      legacyProject: data.legacyProject,
      method: data.method,
      name: data.name,
      payload: data.payload,
      url: data.url,
      description: data.description
    };
    if (data.legacyProject !== this.projectId) {
      this.set('projectId', data.legacyProject);
    }
    this.set('request', base);
  },
  // Restores latest request from local storage.
  restoreLatest: function() {
    this.$.latest.read();
  },

  // Handler for latest rectore call.
  _latestLoaded: function(e) {
    var obj = e.detail.value;
    if (!obj) {
      obj = {
        method: 'GET'
      };
    }
    this.set('request', obj);
  },

  _requestChanged: function(record) {
    if (!record || !record.base) {
      return;
    }
    var request = record.base;
    if (Object.keys(request).length === 1 && request.method && request.method === 'GET') {
      // Default value.
      return;
    }
    this.debounce('request-store', function() {
      this.$.latest.store();
    }, 100);
  },
  /**
   * Handler for the save event from the request editor.
   */
  _saveHandler: function(e) {
    this.onSave({});
    e.preventDefault();
    e.stopPropagation();
  },

  onSave: function(opts) {
    if (!this.request.url || !this.request.url.trim()) {
      return;
    }
    var type = this.request.type;
    var isDrive;
    var isSaved;
    if (type) {
      isDrive = type === 'google-drive';
      isSaved = type === 'saved';
    } else {
      isDrive = !!this.routeParams.externalId;
      isSaved = !!this.routeParams.savedId;
    }
    if ((isDrive || isSaved) && opts.source === 'shortcut' && !opts.shift) {
      return this._saveCurrent();
    }

    this.$.requestEditor.request = this.request;
    this.$.requestEditorContainer.opened = true;
    this.fire('send-analytics', {
      'type': 'event',
      'category': 'Engagement',
      'action': 'Click',
      'label': 'Save action initialization'
    });
  },

  _resizeEditorSheetContent: function() {
    this.$.requestEditor.notifyResize();
  },

  _cancelRequestEdit: function() {
    this.$.requestEditorContainer.opened = false;
    this.$.requestEditor.request = undefined;
  },

  _saveCurrent: function() {
    var data = Object.assign({}, this.proxyRequest, this.request);
    var opts = {
      isDrive: this.request.type === 'google-drive',
      projectId: this.projectId,
      name: data.name,
      description: data.description
    };

    this._dispatchSaveRequest(opts, data);
    this.fire('send-analytics', {
      'type': 'event',
      'category': 'Engagement',
      'action': 'Click',
      'label': 'Save express'
    });
  },

  /**
   * Prepares a detail object for the `save-request-data` event.
   *
   * @param {Object} opts Map of saving request options returned from save
   *                      panel / dialog
   * @param {Object} request The request object to update.
   * @return {Object} Event's detail object.
   */
  _prepareSaveRequestEvent: function(opts, request) {
    var data = Object.assign({}, request);
    if (opts.name) {
      data.name = opts.name;
    }
    if (opts.description) {
      data.description = opts.description;
    } else if (data.description) {
      delete data.description;
    }
    if (opts.override === false || opts.type === 'history') {
      delete data._id;
      delete data._rev;
    }
    var detail = {
      request: data,
      opts: opts
    };
    detail.opts.drive = opts.isDrive ? true : false;
    delete opts.isDrive;
    return detail;
  },

  /**
   * Dispatches the event to save request data in the data store.
   * @param {CustomEvent} e The `save-request` event dispatched from the editor.
   */
  _saveRequestEdit: function(e) {
    this.$.requestEditorContainer.opened = false;
    this.$.requestEditor.request = undefined;
    this._dispatchSaveRequest(e.detail, e.detail.request);
  },

  _dispatchSaveRequest: function(opts, data) {
    var detail = this._prepareSaveRequestEvent(opts, data);

    var event = this.fire('save-request-data', detail, {
      cancelable: true,
      composed: true
    });

    if (!event.defaultPrevented) {
      let message = 'Unable to save current request.\n';
      message += 'The "save-request-data" event is not handled.';
      console.error(message);
      this.fire('app-log', {
        'message': [message],
        'level': 'error'
      });
      this.$.errorToast.text = message;
      this.$.errorToast.opened = true;
      return Promise.reject(new Error('Save event not handled'));
    }

    // if (opts.isProject) {
    //   if (opts.projectIsNew) {
    //     detail.opts.projectName = opts.projectName;
    //   } else {
    //     detail.opts.projectId = opts.projectId;
    //   }
    // }

    return event.detail.result
    .then(request => {
      this.$.savedOk.opened = true;
      if (request.legacyProject && request.legacyProject !== this.projectId) {
        this.set('projectId', request.legacyProject);
      } else if (!request.legacyProject && this.projectId) {
        this.set('projectId', undefined);
      }
      this.proxyRequest = data;
      var id = request._id;
      try {
        id = encodeURIComponent(id);
      } catch (e) {}
      this.set('routeParams', {
        savedId: id,
        type: 'saved'
      });
    })
    .catch(cause => {
      console.error('Save request error', cause);
      this.fire('app-log', {
        'message': ['Unable to save the request.', cause],
        'level': 'error'
      });
      this.$.errorToast.text = 'Unable to save current request. ' + cause.message;
      this.$.errorToast.opened = true;
    });
  },
  /**
   * Inform other elements that the project ID changed.
   */
  _projectIdChanged: function(id) {
    this.fire('selected-project-changed', {
      value: id
    });
  },
  /**
   * Handles `report-response` custom event and displays cookie banner
   * if the response needs authorization.
   */
  _reportRequestHandler: function(e) {
    if (e.detail.auth) {
      this.cookieBanner = true;
    }
  },
  // Closes cookie banner upon user request.
  _closeCookieBanner: function() {
    this.cookieBanner = false;
  }
});
<commit_msg>New: Added option to restore multipart data.<commit_after>Polymer({
  is: 'arc-request-panel',

  properties: {
    /**
     * Current route params.
     */
    routeParams: Object,
    /**
     * Set by parent element to determine that this view is opened.
     */
    opened: Boolean,
    /**
     * Set by the `chrome-xhr-request` element. True if the proxy extension
     * is installed an connected to the application.
     */
    hasProxyInstalled: Boolean,
    /**
     * Current request object.
     */
    request: Object,
    /**
     * Keeps temp request data restored from the datastore / local store
     * to sync state between different stores and the UI.
     */
    proxyRequest: Object,
    /**
     * If set the panel will use the XHR proxy to send the request
     */
    useXhr: Boolean,
    // Currently selected project.
    projectId: String,
    // True to display cookie exchange instalkl banner.
    cookieBanner: Boolean
  },

  listeners: {
    'transport-request': '_onTransportRequested',
    'abort-request': '_abortHandler',
    'request-clear-state': '_clear'
  },

  observers: [
    '_routeChnaged(routeParams, opened)',
    '_openedChanged(opened)',
    '_proxyRequestChanged(proxyRequest.*)',
    '_requestChanged(request.*)',
    '_projectIdChanged(projectId)'
  ],

  attached: function() {
    this.listen(window, 'report-response', '_reportRequestHandler');
  },

  detached: function() {
    this.unlisten(window, 'report-response', '_reportRequestHandler');
  },

  _clear: function() {
    this.cookieBanner = false;
  },

  _routeChnaged: function(route, opened) {
    if (!opened || !route || !route.type) {
      return;
    }
    switch (route.type) {
      case 'saved':
      case 'history':
        let id = decodeURIComponent(this.routeParams.savedId || this.routeParams.historyId);
        this._restoreRequestData(id, route.type);
        break;
      case 'current':
      case 'drive':
        return;
      default:
        this.restoreLatest();
        break;
    }
  },

  _openedChanged: function(opened) {
    if (opened) {
      this._requestFeatures();
    } else {
      this._releaseFeatures();
    }
  },

  _requestFeatures: function() {
    this.fire('request-toolbar-features', {
      features: ['clearAll', 'save', 'xhrtoggle']
    }, {
      cancelable: true
    });
  },

  _releaseFeatures: function() {
    this.fire('release-toolbar-features', {}, {
      cancelable: true
    });
  },

  _onTransportRequested: function(e) {
    if (this.cookieBanner) {
      this.cookieBanner = false;
    }
    this.fire('url-history-store', {
      value: this.request.url
    }, {
      cancelable: true,
      composed: true
    });
    var panel;
    if (this.useXhr) {
      panel = this.$$('chrome-xhr-request');
    } else {
      panel = this.$$('chrome-socket-request');
    }
    panel.send(e.detail)
    .catch(cause => {
      console.error(cause);
    });
  },
  /**
   * Aborts the request.
   */
  _abortHandler: function() {
    var panel;
    if (this.useXhr) {
      panel = this.$$('chrome-xhr-request');
    } else {
      panel = this.$$('chrome-socket-request');
    }
    panel.abort();
    this.fire('url-history-store', {
      value: this.request.url
    }, {
      cancelable: true,
      composed: true
    });
  },
  /**
   * Handler for the XHR change event
   */
  _xhrChanged: function(e) {
    var requested = e.detail.value;
    e.preventDefault();
    e.stopPropagation();

    if (!this.hasProxyInstalled && requested) {
      this.$$('install-proxy-dialog').opened = true;
      return;
    }
    this.useXhr = requested;
    this.fire('send-analytics', {
      type: 'event',
      category: 'Request',
      action: 'Use XHR',
      label: requested + '',
    });
  },
  /**
   * Clears the request state
   */
  clearRequest: function(e) {
    this.set('request', {
      method: 'GET'
    });
    this.projectId = undefined;
    if (e && e.preventDefault) {
      e.preventDefault();
      e.stopPropagation();
    }
  },
  /**
   * Sends the request from the shortcut manager.
   * TODO: It uses private APIs and it shouldn't.
   */
  sendRequest: function() {
    var panel = this.$$('request-panel');
    var editor = panel.$$('request-editor');
    editor._sendRequest();
  },
  /**
   * Restores saved in the datastore request data.
   * @param {String} id ID of the record
   * @param {String} type Type of the stored data. Can be `saved`, `history` or
   * `drive`
   */
  _restoreRequestData: function(id, type) {
    type = type || 'saved';
    var eventType;
    switch (type) {
      case 'saved':
        eventType = 'saved-requests';
        break;
      case 'history':
        eventType = 'history-requests';
        break;
      case 'drive':
        eventType = 'saved-requests';
        break;
      default:
        this.fire('app-log', {
          'message': `${type} is not a type of a request.`,
          'level': 'error'
        });
        console.error('Can\'t restore request data. Type is unknown.', type);
        this.fire('send-analytics', {
          type: 'exception',
          description: 'Can\'t restore request of a type: ' + type + ' (ArcRequestPanel)',
          fatal: true
        });
        this.$.typeMissingToast.opened = true;
        return;
    }

    var event = this.fire('request-object-read', {
      id: id,
      type: eventType
    }, {
      cancelable: true
    });

    return event.detail.result
    .then(result => {
      result.type = type;
      this.set('proxyRequest', result);
    })
    .catch((e) => {
      this.fire('app-log', {
        'message': e,
        'level': 'error'
      });
      console.error('Can\'t restore request data', e);
      this.$.missingRequestToast.opened = true;
    });
  },
  /**
   * Sets the `request` object when a proxy change.
   */
  _proxyRequestChanged: function(record) {
    if (!record || !record.base) {
      return;
    }
    var data = record.base;
    var base = {
      _id: data._id,
      type: data.type,
      headers: data.headers,
      legacyProject: data.legacyProject,
      method: data.method,
      name: data.name,
      payload: data.payload,
      url: data.url,
      description: data.description
    };
    if (data.multipart) {
      this._restoreMultipart(base, data.multipart);
    }
    if (data.legacyProject !== this.projectId) {
      this.set('projectId', data.legacyProject);
    }
    this.set('request', base);
  },
  // Restores latest request from local storage.
  restoreLatest: function() {
    this.$.latest.read();
  },

  // Handler for latest rectore call.
  _latestLoaded: function(e) {
    var obj = e.detail.value;
    if (!obj) {
      obj = {
        method: 'GET'
      };
    }
    this.set('request', obj);
  },

  _requestChanged: function(record) {
    if (!record || !record.base) {
      return;
    }
    var request = record.base;
    if (Object.keys(request).length === 1 && request.method && request.method === 'GET') {
      // Default value.
      return;
    }
    this.debounce('request-store', function() {
      this.$.latest.store();
    }, 100);
  },
  /**
   * Handler for the save event from the request editor.
   */
  _saveHandler: function(e) {
    this.onSave({});
    e.preventDefault();
    e.stopPropagation();
  },

  onSave: function(opts) {
    if (!this.request.url || !this.request.url.trim()) {
      return;
    }
    var type = this.request.type;
    var isDrive;
    var isSaved;
    if (type) {
      isDrive = type === 'google-drive';
      isSaved = type === 'saved';
    } else {
      isDrive = !!this.routeParams.externalId;
      isSaved = !!this.routeParams.savedId;
    }
    if ((isDrive || isSaved) && opts.source === 'shortcut' && !opts.shift) {
      return this._saveCurrent();
    }

    this.$.requestEditor.request = this.request;
    this.$.requestEditorContainer.opened = true;
    this.fire('send-analytics', {
      'type': 'event',
      'category': 'Engagement',
      'action': 'Click',
      'label': 'Save action initialization'
    });
  },

  _resizeEditorSheetContent: function() {
    this.$.requestEditor.notifyResize();
  },

  _cancelRequestEdit: function() {
    this.$.requestEditorContainer.opened = false;
    this.$.requestEditor.request = undefined;
  },

  _saveCurrent: function() {
    var data = Object.assign({}, this.proxyRequest, this.request);
    var opts = {
      isDrive: this.request.type === 'google-drive',
      projectId: this.projectId,
      name: data.name,
      description: data.description
    };

    this._dispatchSaveRequest(opts, data);
    this.fire('send-analytics', {
      'type': 'event',
      'category': 'Engagement',
      'action': 'Click',
      'label': 'Save express'
    });
  },

  /**
   * Prepares a detail object for the `save-request-data` event.
   *
   * @param {Object} opts Map of saving request options returned from save
   *                      panel / dialog
   * @param {Object} request The request object to update.
   * @return {Object} Event's detail object.
   */
  _prepareSaveRequestEvent: function(opts, request) {
    var data = Object.assign({}, request);
    if (opts.name) {
      data.name = opts.name;
    }
    if (opts.description) {
      data.description = opts.description;
    } else if (data.description) {
      delete data.description;
    }
    if (opts.override === false || opts.type === 'history') {
      delete data._id;
      delete data._rev;
    }
    var detail = {
      request: data,
      opts: opts
    };
    detail.opts.drive = opts.isDrive ? true : false;
    delete opts.isDrive;
    return detail;
  },

  /**
   * Dispatches the event to save request data in the data store.
   * @param {CustomEvent} e The `save-request` event dispatched from the editor.
   */
  _saveRequestEdit: function(e) {
    this.$.requestEditorContainer.opened = false;
    this.$.requestEditor.request = undefined;
    this._dispatchSaveRequest(e.detail, e.detail.request);
  },

  _dispatchSaveRequest: function(opts, data) {
    var detail = this._prepareSaveRequestEvent(opts, data);

    var event = this.fire('save-request-data', detail, {
      cancelable: true,
      composed: true
    });

    if (!event.defaultPrevented) {
      let message = 'Unable to save current request.\n';
      message += 'The "save-request-data" event is not handled.';
      console.error(message);
      this.fire('app-log', {
        'message': [message],
        'level': 'error'
      });
      this.$.errorToast.text = message;
      this.$.errorToast.opened = true;
      return Promise.reject(new Error('Save event not handled'));
    }

    // if (opts.isProject) {
    //   if (opts.projectIsNew) {
    //     detail.opts.projectName = opts.projectName;
    //   } else {
    //     detail.opts.projectId = opts.projectId;
    //   }
    // }

    return event.detail.result
    .then(request => {
      this.$.savedOk.opened = true;
      if (request.legacyProject && request.legacyProject !== this.projectId) {
        this.set('projectId', request.legacyProject);
      } else if (!request.legacyProject && this.projectId) {
        this.set('projectId', undefined);
      }
      this.proxyRequest = data;
      var id = request._id;
      try {
        id = encodeURIComponent(id);
      } catch (e) {}
      this.set('routeParams', {
        savedId: id,
        type: 'saved'
      });
    })
    .catch(cause => {
      console.error('Save request error', cause);
      this.fire('app-log', {
        'message': ['Unable to save the request.', cause],
        'level': 'error'
      });
      this.$.errorToast.text = 'Unable to save current request. ' + cause.message;
      this.$.errorToast.opened = true;
    });
  },
  /**
   * Inform other elements that the project ID changed.
   */
  _projectIdChanged: function(id) {
    this.fire('selected-project-changed', {
      value: id
    });
  },
  /**
   * Handles `report-response` custom event and displays cookie banner
   * if the response needs authorization.
   */
  _reportRequestHandler: function(e) {
    if (e.detail.auth) {
      this.cookieBanner = true;
    }
  },
  // Closes cookie banner upon user request.
  _closeCookieBanner: function() {
    this.cookieBanner = false;
  },
  /**
   * Restores a multipart object.
   * @param {Object} base Request object
   * @param {Array} data Multipart data
   */
  _restoreMultipart: function(base, data) {
    var fd = new FormData();
    data.forEach(part => {
      let name = part.name;
      let value;
      if (part.isFile) {
        try {
          value = this._dataURLtoBlob(part.value);
        } catch (e) {
          value = '';
        }
      } else {
        value = part.value;
      }
      fd.append(name, value);
    });
    base.payload = fd;
  },

  _dataURLtoBlob: function(dataurl) {
    var arr = dataurl.split(',');
    var mime = arr[0].match(/:(.*?);/)[1];
    var bstr = atob(arr[1]);
    var n = bstr.length;
    var u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type: mime});
  }
});
<|endoftext|>


<commit_before>var Q = require('q');
var _ = require('lodash');
var pipelineRunCreator = require('./pipelineRun');
var globalOptions = require('../options');

function pipelineFeedReaderModule() {

  var CACHE;

  var clearCache = function() {
    CACHE = {
      pipelineRuns: {},
      statistics: {}
    };
  };
  clearCache();

  var MIN_NUMBER_PIPELINE_RUNS = 50;

  function addChangedFilesToData(pipelineJson, pipelineDetails) {
    _.each(pipelineDetails, function (detail) {

      if(detail !== undefined && detail.pipeline !== undefined) {
        var pipelineDataToExtend = _.find(pipelineJson.pipelines, function (pipeline) {
          return pipeline.label === detail.pipeline.label + '';
        });

        if (pipelineDataToExtend) {
          function extractChangeSets(pipelineDetail) { // because stupid xml2json
            var materials = [].concat(pipelineDetail.materials.material);
            return _.flatten(_.map(materials, function (m) {
              var allModifications = m ? [].concat(m.modifications) : [];
              return _.map(allModifications, function (mod) {
                return [].concat(mod.changeset);
              });
            }));
          }

          pipelineDataToExtend['build_cause'].files = _.flatten(_.pluck(extractChangeSets(detail.pipeline), 'file'));

        }
      }
    });
  }

  function updateSummaryWithDependencies() {
    _.each(_.keys(CACHE.pipelineRuns), function(pipelineName) {
      _.each(_.keys(CACHE.pipelineRuns[pipelineName]), function(pipelineLabel) {
        var pipelineRun = CACHE.pipelineRuns[pipelineName][pipelineLabel];
        if(pipelineRun.summary.upstream) {
          var pipelineRuns = CACHE.pipelineRuns[pipelineRun.summary.upstream.pipelineName];
          if(pipelineRuns === undefined) {
            console.log('Could not derive change info from upstream pipeline, maybe it was renamed?', JSON.stringify(pipelineRun.summary.upstream));
            return;
          }

          var upstreamRun = pipelineRuns[pipelineRun.summary.upstream.pipelineLabel];
          if(upstreamRun === undefined) {
            console.log('Could not derive change info from upstream pipeline, info is probably too far back in history', JSON.stringify(pipelineRun.summary.upstream));
          } else {
            pipelineRun.summary.withMaterialRevision(upstreamRun.findChangedMaterialRevision());
          }
        }
      });
    });
  }

  var requestHistoryAndDetails = function (offset, pipelineName) {
    return globalOptions.getHistoryRequestor().getHistory(offset, pipelineName).then(function(json) {
      if(json !== undefined) {
        var detailsPromises = _.map(json.pipelines, function (pipelineData) {
          return globalOptions.getHistoryRequestor().getPipelineDetails(pipelineData.id || pipelineData.counter, pipelineName);
        });
        return Q.all(detailsPromises).then(function (details) {
          addChangedFilesToData(json, details);
          return json;
        });
      } else {
        return json;
      }

    });

  };

  var initFullHistoryCache = function(pipelineNames) {

    console.log("CACHING FULL HISTORY for ", pipelineNames);
    var pipelineRefreshes = _.map(pipelineNames, function (pipelineName) {
      CACHE.pipelineRuns[pipelineName] = CACHE.pipelineRuns[pipelineName] || {};
      return refreshHistoryForPipeline({ pipelineName: pipelineName });
    });

    return Q.all(pipelineRefreshes).then(function() {
      updateSummaryWithDependencies();
    });

  };

  var refreshHistoryForPipeline = function (options, originalDefer) {
    var pipelineName = options.pipelineName;
    var offset = options.offset;
    var maxNumberOfPages = options.maxNumberOfPages;

    var defer = originalDefer || Q.defer();

    requestHistoryAndDetails(offset, pipelineName).then(function (result) {
      function fillCache(runEntry) {

        var pipelineInCache = CACHE.pipelineRuns[pipelineName] && CACHE.pipelineRuns[pipelineName][runEntry.label];

        var cacheIsNewerThanRunEntry = pipelineInCache && CACHE.pipelineRuns[pipelineName][runEntry.label]['natural_order'] > runEntry['natural_order'];
        if(cacheIsNewerThanRunEntry) {
          return;
        }

        if(! pipelineInCache) {
          CACHE.pipelineRuns[pipelineName] = CACHE.pipelineRuns[pipelineName] || {};
          var pipelineRun = pipelineRunCreator.pipelineRun.createNew(runEntry);
          CACHE.pipelineRuns[pipelineName][runEntry.label] = pipelineRun;

        } else {
          [CACHE.pipelineRuns[pipelineName][runEntry.label].update(runEntry)];
        }
      }

      function continueToNextPageOrResolve() {

        var cacheSize = _.keys(CACHE.pipelineRuns[pipelineName]).length;
        var pageNumber = result.pagination.offset + 1;
        var needsNextPage = result.pagination !== undefined
            && maxNumberOfPages ? pageNumber < maxNumberOfPages : true
            && result.pagination.total > cacheSize
            && cacheSize < MIN_NUMBER_PIPELINE_RUNS
            && result.pipelines.length > 0;
        if (needsNextPage) {
          var nextOffset = result.pagination.offset + result.pagination['page_size'];
          refreshHistoryForPipeline({ pipelineName: pipelineName, offset: nextOffset, maxNumberOfPages: maxNumberOfPages }, defer);
        } else {
          defer.resolve();
        }

      }

      if(result !== undefined) {
        _.each(result.pipelines, fillCache);
        continueToNextPageOrResolve();
      }

    }).done();

    return defer.promise;

  };

  var readHistory = function(options) {
    options = options || {};
    options.exclude = options.exclude || [];

    if(! options.pipeline) {
      throw new Error('Cannot determine which pipeline data to return. Please provide pipeline name');
    }

    return refreshHistoryForPipeline({ pipelineName: options.pipeline, offset: 0, maxNumberOfPages: 1}).then(function() {
      function excludeSpecificRuns(pipelineRuns) {
        return _.omit(pipelineRuns, function (value, key) {
          return _.contains(options.exclude, key);
        });
      }

      return {
        pipelineRuns: excludeSpecificRuns(CACHE.pipelineRuns[options.pipeline]),
        statistics: CACHE.statistics
      };
    });

  };

  return {
    initFullCache: initFullHistoryCache,
    readHistory: readHistory,
    clear: clearCache
  };

}

var pipelineFeedReader = pipelineFeedReaderModule();
module.exports.initFullCache = pipelineFeedReader.initFullCache;
module.exports.readHistory = pipelineFeedReader.readHistory;
module.exports.clear = pipelineFeedReader.clear;
<commit_msg>Fix internal data structure of the cache<commit_after>var Q = require('q');
var _ = require('lodash');
var pipelineRunCreator = require('./pipelineRun');
var globalOptions = require('../options');

function pipelineFeedReaderModule() {

  var CACHE;

  var clearCache = function() {
    CACHE = { };
  };
  clearCache();

  var MIN_NUMBER_PIPELINE_RUNS = 50;

  function addChangedFilesToData(pipelineJson, pipelineDetails) {
    _.each(pipelineDetails, function (detail) {

      if(detail !== undefined && detail.pipeline !== undefined) {
        var pipelineDataToExtend = _.find(pipelineJson.pipelines, function (pipeline) {
          return pipeline.label === detail.pipeline.label + '';
        });

        if (pipelineDataToExtend) {
          function extractChangeSets(pipelineDetail) { // because stupid xml2json
            var materials = [].concat(pipelineDetail.materials.material);
            return _.flatten(_.map(materials, function (m) {
              var allModifications = m ? [].concat(m.modifications) : [];
              return _.map(allModifications, function (mod) {
                return [].concat(mod.changeset);
              });
            }));
          }

          pipelineDataToExtend['build_cause'].files = _.flatten(_.pluck(extractChangeSets(detail.pipeline), 'file'));

        }
      }
    });
  }

  function updateSummaryWithDependencies() {
    _.each(_.keys(CACHE), function(pipelineName) {
      _.each(_.keys(CACHE[pipelineName].pipelineRuns), function(pipelineLabel) {
        var pipelineRun = CACHE[pipelineName].pipelineRuns[pipelineLabel];
        if(pipelineRun.summary.upstream) {
          var pipelineRuns = CACHE[pipelineRun.summary.upstream.pipelineName].pipelineRuns;
          if(pipelineRuns === undefined) {
            console.log('Could not derive change info from upstream pipeline, maybe it was renamed?', JSON.stringify(pipelineRun.summary.upstream));
            return;
          }

          var upstreamRun = pipelineRuns[pipelineRun.summary.upstream.pipelineLabel];
          if(upstreamRun === undefined) {
            console.log('Could not derive change info from upstream pipeline, info is probably too far back in history', JSON.stringify(pipelineRun.summary.upstream));
          } else {
            pipelineRun.summary.withMaterialRevision(upstreamRun.findChangedMaterialRevision());
          }
        }
      });
    });
  }

  var requestHistoryAndDetails = function (offset, pipelineName) {
    return globalOptions.getHistoryRequestor().getHistory(offset, pipelineName).then(function(json) {
      if(json !== undefined) {
        var detailsPromises = _.map(json.pipelines, function (pipelineData) {
          return globalOptions.getHistoryRequestor().getPipelineDetails(pipelineData.id || pipelineData.counter, pipelineName);
        });
        return Q.all(detailsPromises).then(function (details) {
          addChangedFilesToData(json, details);
          return json;
        });
      } else {
        return json;
      }

    });

  };

  var initFullHistoryCache = function(pipelineNames) {

    console.log("CACHING FULL HISTORY for ", pipelineNames);
    var pipelineRefreshes = _.map(pipelineNames, function (pipelineName) {
      CACHE[pipelineName] = CACHE[pipelineName] || {};
      CACHE[pipelineName].pipelineRuns = CACHE[pipelineName].pipelineRuns || {};
      return refreshHistoryForPipeline({ pipelineName: pipelineName });
    });

    return Q.all(pipelineRefreshes).then(function() {
      updateSummaryWithDependencies();
    });

  };

  var refreshHistoryForPipeline = function (options, originalDefer) {
    var pipelineName = options.pipelineName;
    var offset = options.offset;
    var maxNumberOfPages = options.maxNumberOfPages;

    var defer = originalDefer || Q.defer();

    requestHistoryAndDetails(offset, pipelineName).then(function (result) {
      function fillCache(runEntry) {

        CACHE[pipelineName] = CACHE[pipelineName] || {};
        var pipelineInCache = CACHE[pipelineName].pipelineRuns && CACHE[pipelineName].pipelineRuns[runEntry.label];

        var cacheIsNewerThanRunEntry = pipelineInCache && CACHE[pipelineName].pipelineRuns[runEntry.label]['natural_order'] > runEntry['natural_order'];
        if(cacheIsNewerThanRunEntry) {
          return;
        }

        if(! pipelineInCache) {
          CACHE[pipelineName].pipelineRuns = CACHE[pipelineName].pipelineRuns || {};
          var pipelineRun = pipelineRunCreator.pipelineRun.createNew(runEntry);
          CACHE[pipelineName].pipelineRuns[runEntry.label] = pipelineRun;

        } else {
          [CACHE[pipelineName].pipelineRuns[runEntry.label].update(runEntry)];
        }
      }

      function continueToNextPageOrResolve() {
        var cacheSize = CACHE[pipelineName] ? _.keys(CACHE[pipelineName].pipelineRuns).length : 0;
        var pageNumber = result.pagination.offset + 1;
        var needsNextPage = result.pagination !== undefined
            && maxNumberOfPages ? pageNumber < maxNumberOfPages : true
            && result.pagination.total > cacheSize
            && cacheSize < MIN_NUMBER_PIPELINE_RUNS
            && result.pipelines.length > 0;
        if (needsNextPage) {
          var nextOffset = result.pagination.offset + result.pagination['page_size'];
          refreshHistoryForPipeline({ pipelineName: pipelineName, offset: nextOffset, maxNumberOfPages: maxNumberOfPages }, defer);
        } else {
          defer.resolve();
        }

      }

      if(result !== undefined) {
        _.each(result.pipelines, fillCache);
        continueToNextPageOrResolve();
      }

    }).done();

    return defer.promise;

  };

  var readHistory = function(options) {
    options = options || {};
    options.exclude = options.exclude || [];

    if(! options.pipeline) {
      throw new Error('Cannot determine which pipeline data to return. Please provide pipeline name');
    }

    return refreshHistoryForPipeline({ pipelineName: options.pipeline, offset: 0, maxNumberOfPages: 1}).then(function() {
      function excludeSpecificRuns(pipelineRuns) {
        return _.omit(pipelineRuns, function (value, key) {
          return _.contains(options.exclude, key);
        });
      }

      return {
        pipelineRuns: CACHE[options.pipeline] ? excludeSpecificRuns(CACHE[options.pipeline].pipelineRuns) : {},
        statistics: CACHE.statistics
      };
    });

  };

  return {
    initFullCache: initFullHistoryCache,
    readHistory: readHistory,
    clear: clearCache
  };

}

var pipelineFeedReader = pipelineFeedReaderModule();
module.exports.initFullCache = pipelineFeedReader.initFullCache;
module.exports.readHistory = pipelineFeedReader.readHistory;
module.exports.clear = pipelineFeedReader.clear;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Setup nodejs-like 'global' on web
var global = global || this;

// Top-Level of foam package
foam = { array: {}, fn: {}, string: {}, util: {} };

foam.fn.memoize1 = function memoize1(f) {
  // Faster version of memoize() when only dealing with one argument.
  var cache = {};
  var g = function(arg) {
    console.assert(arguments.length == 1, "Memoize1'ed functions must take exactly one argument.");
    var key = arg ? arg.toString() : '';
    if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg);
    return cache[key];
  };
  foam.fn.setName(g, 'memoize1(' + f.name + ')');
  return g;
};

foam.fn.setName = function setName(f, name) {
  // Set a function's name for improved debugging and profiling
  //Object.defineProperty(f, 'name', {value: name, configurable: true}); //TODO: re-enable if supported
};

/** convenience method to append 'arguments' onto a real array */
foam.fn.appendArguments = function appendArguments(a, args, start) {
  for ( var i = start ; i < args.length ; i++ ) a.push(args[i]);
  return a;
},

/** convenience method to turn 'arguments' into a real array */
foam.fn.argsToArray = function argsToArray(args) {
  return foam.fn.appendArguments([], args, 0);
},

foam.string.constantize = foam.fn.memoize1(function(str) {
  // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT
  return str.replace(/[a-z][^0-9a-z_]/g, function(a) {
    return a.substring(0,1) + '_' + a.substring(1,2);
  }).toUpperCase();
});

foam.string.pad = function(str, size) {
  // Right pads to size if size > 0, Left pads to -size if size < 0
  return size < 0 ?
    (new Array(-size).join(' ') + str).slice(size)       :
    (str + new Array(size).join(' ')).substring(0, size) ;
};

foam.events = {

  oneTime: function oneTime(listener) {
    /** Create a "one-time" listener which unsubscribes itself after its first invocation. **/
    return function(subscription) {
      listener.apply(this, foam.fn.argsToArray(arguments));
      subscription.destroy();
    };
  },

  consoleLog: function consoleLog(listener) {
    /** Log all listener invocations to console. **/
    return function() {
      var args = foam.fn.argsToArray(arguments);
      console.log(args);

      listener.apply(this, args);
    };
  }
};
<commit_msg>Restructured stdlib to use new foam.LIB() feature.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Setup nodejs-like 'global' on web
var global = global || this;

// Top-Level of foam package
foam = {
  Object:   Object.prototype,
  Function: Function.prototype,
  Array:    Array.prototype
};

foam.LIB = function LIB(model) {
  var proto;

  function defineProperty(proto, key, map) {
    console.log('defineProperty: ', key);

    if ( ! map.value || proto === Object.prototype || proto === Array.prototype )
      Object.defineProperty.apply(this, arguments);
    else
      proto[key] = map.value;
  }

  proto = foam[model.name] || ( foam[model.name] = {} );

  if ( model.properties ) for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var p = model.properties[i];
    defineProperty(
      proto,
      p.name,
      { get: p.getter, enumerable: false });
  }

  for ( key in model.constants )
    defineProperty(
      proto,
      key,
      { value: model.constants[key], writable: true, enumerable: false });

  if ( model.methods ) for ( var i = 0 ; i < model.methods.length ; i++ ) {
    var m = model.methods[i];
    defineProperty(
      proto,
      m.name,
      { value: m.code || m, writable: true, enumerable: false });
  }
};

foam.LIB({
  name: 'Object',

  properties: [
    {
      name: '$UID',
      getter: (function() {
        var id = 1;
        return function() {
          if ( Object.hasOwnProperty.call(this, '$UID__') ) return this.$UID__;
          this.$UID__ = id;
          id++;
          return this.$UID__;
        };
      })()
    }
  ]
});

foam.LIB({
  name: 'array',

  methods: [
    function argsToArray(args) {
      /** convenience method to turn 'arguments' into a real array */
      return foam.fn.appendArguments([], args, 0);
    }
  ]
});

foam.LIB({
  name: 'events',

  methods: [
    function oneTime(listener) {
      /** Create a "one-time" listener which unsubscribes itself after its first invocation. **/
      return function(subscription) {
        listener.apply(this, foam.array.argsToArray(arguments));
        subscription.destroy();
      };
    },

    function consoleLog(listener) {
      /** Log all listener invocations to console. **/
      return function() {
        var args = foam.array.argsToArray(arguments);
        console.log(args);
        listener.apply(this, args);
      };
    }
  ]
});

foam.LIB({
  name: 'fn',

  methods: [
    function memoize1(f) {
      // Faster version of memoize() when only dealing with one argument.
      var cache = {};
      var g = function(arg) {
        console.assert(arguments.length == 1, "Memoize1'ed functions must take exactly one argument.");
        var key = arg ? arg.toString() : '';
        if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg);
        return cache[key];
      };
      foam.fn.setName(g, 'memoize1(' + f.name + ')');
      return g;
    },

    function setName(f, name) {
      // Set a function's name for improved debugging and profiling
      //Object.defineProperty(f, 'name', {value: name, configurable: true}); //TODO: re-enable if supported
    },

    function appendArguments(a, args, start) {
      /** convenience method to append 'arguments' onto a real array */
      for ( var i = start ; i < args.length ; i++ ) a.push(args[i]);
      return a;
    }
  ]
});

foam.LIB({
  name: 'string',

  methods: [
    {
      name: 'constantize',
      code: foam.fn.memoize1(function(str) {
        // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT
        return str.replace(/[a-z][^0-9a-z_]/g, function(a) {
          return a.substring(0,1) + '_' + a.substring(1,2);
        }).toUpperCase();
      })
    },

    function pad(str, size) {
      // Right pads to size if size > 0, Left pads to -size if size < 0
      return size < 0 ?
        (new Array(-size).join(' ') + str).slice(size)       :
        (str + new Array(size).join(' ')).substring(0, size) ;
    }
  ]
});
<|endoftext|>


<commit_before>'use strict'

const url = require('url')
const {is} = require('type-is')
const {pipe} = require('mississippi')
const htmlparser = require('htmlparser2')
const normalize = require('normalize-url')

const bufferize = require('../bufferize')

function stripQuery(location) {
  const parsed = new url.URL(location)
  parsed.search = ''

  try {
    return normalize(parsed.toString(), {
      stripWWW: false,
      removeTrailingSlash: false
    })
  } catch (error) {
    // Url could not be normalized, return original URL.
    // See https://github.com/sindresorhus/normalize-url/issues/24
    return parsed.toString()
  }
}

function extractLinks(token, options) {
  let isIndexOf = false
  const links = []

  let baseUrl = stripQuery(token.finalUrl)

  const parser = new htmlparser.WritableStream({
    ontext: text => {
      const matches = options.indexOfMatches || []

      if (matches.some(match => text.trim().match(match))) {
        isIndexOf = true
      }
    },

    onopentag: (tag, attributes) => {
      if (tag === 'base' && attributes.href) {
        baseUrl = stripQuery(url.resolve(baseUrl, attributes.href))
      }

      if (isIndexOf && tag === 'a' && attributes.href) {
        const resolved = stripQuery(url.resolve(baseUrl, attributes.href))

        if (resolved !== baseUrl && resolved.startsWith(baseUrl)) {
          links.push(resolved)
        }
      }
    }
  })

  return new Promise((resolve, reject) => {
    pipe(
      token.response,
      parser,
      err => {
        if (err) {
          return reject(err)
        }

        resolve({isIndexOf, links})
      }
    )
  })
}

async function analyzeIndexOf(token, options) {
  if (token.analyzed) {
    return false
  }

  const isHtml = token.fileTypes.some(type => is(type.mime, 'html'))
  if (!isHtml) {
    return false
  }

  options.logger.log('index-of:analyze:start', token)
  const {isIndexOf, links} = await bufferize(token, () => extractLinks(token, options))
  options.logger.log('index-of:analyze:end', token)

  if (isIndexOf) {
    token.type = 'index-of'
    token.analyzed = true
    token.children = []

    if (links.length > 0) {
      token.children = links.map(url => ({
        inputType: 'url',
        url
      }))
    }
  }
}

module.exports = analyzeIndexOf
<commit_msg>Set stripHash to true<commit_after>'use strict'

const url = require('url')
const {is} = require('type-is')
const {pipe} = require('mississippi')
const htmlparser = require('htmlparser2')
const normalize = require('normalize-url')

const bufferize = require('../bufferize')

function stripQuery(location) {
  const parsed = new url.URL(location)
  parsed.search = ''

  try {
    return normalize(parsed.toString(), {
      stripWWW: false,
      removeTrailingSlash: false,
      stripHash: true
    })
  } catch (error) {
    // Url could not be normalized, return original URL.
    // See https://github.com/sindresorhus/normalize-url/issues/24
    return parsed.toString()
  }
}

function extractLinks(token, options) {
  let isIndexOf = false
  const links = []

  let baseUrl = stripQuery(token.finalUrl)

  const parser = new htmlparser.WritableStream({
    ontext: text => {
      const matches = options.indexOfMatches || []

      if (matches.some(match => text.trim().match(match))) {
        isIndexOf = true
      }
    },

    onopentag: (tag, attributes) => {
      if (tag === 'base' && attributes.href) {
        baseUrl = stripQuery(url.resolve(baseUrl, attributes.href))
      }

      if (isIndexOf && tag === 'a' && attributes.href) {
        const resolved = stripQuery(url.resolve(baseUrl, attributes.href))

        if (resolved !== baseUrl && resolved.startsWith(baseUrl)) {
          links.push(resolved)
        }
      }
    }
  })

  return new Promise((resolve, reject) => {
    pipe(
      token.response,
      parser,
      err => {
        if (err) {
          return reject(err)
        }

        resolve({isIndexOf, links})
      }
    )
  })
}

async function analyzeIndexOf(token, options) {
  if (token.analyzed) {
    return false
  }

  const isHtml = token.fileTypes.some(type => is(type.mime, 'html'))
  if (!isHtml) {
    return false
  }

  options.logger.log('index-of:analyze:start', token)
  const {isIndexOf, links} = await bufferize(token, () => extractLinks(token, options))
  options.logger.log('index-of:analyze:end', token)

  if (isIndexOf) {
    token.type = 'index-of'
    token.analyzed = true
    token.children = []

    if (links.length > 0) {
      token.children = links.map(url => ({
        inputType: 'url',
        url
      }))
    }
  }
}

module.exports = analyzeIndexOf
<|endoftext|>


<commit_before>var assert = require('assert');
var path = require('path');
var spawn = require('child_process').spawn;

var Tempfile = require('temporary/lib/file');
var request = require('request');
var which = require('which');
var phantomLocation = which.sync('phantomjs');

// DEV: This is a horrible way to organize code. Nothing like this should be run at require time
var phantomServer = spawn(phantomLocation, [__dirname + '/phantomjs/compose.js'],  {customFds: [0, 1, 2]});

module.exports = function getImageInfo (params, cb) {
  assert(params.js, 'JS not provided');
  assert(params.width, 'Width not provided');
  assert(params.height, 'Height not provided');

  // TODO: Consider passing a function or vanilla JS

  // Stringify our argument for phantomjs
  var arg = JSON.stringify(params),
      encodedArg = encodeURIComponent(arg);

  assert(encodedArg.length < 4096, 'JavaScript is over 4096 characters when encoded');

  request({
    url: 'http://localhost:9090/',
    method: 'POST',
    headers: {
      // DEV: PhantomJS looks for Proper-Case headers, request is lower-case =(
      'Content-Length': encodedArg.length
    },
    body: encodedArg
  }, function handlePhantomResponse(err, res, body) {
    // If there was an error, callback with it
    if (err) {
      return cb(err);
    }

    // If there was a bad status code, callback with a good message
    if (res.statusCode < 200 || res.statusCode >= 300) {
      return cb(new Error('PhantomJS server responded with "' + res.statusCode + '" status code. ' + body));
    }

    // Otherwise, callback with the body
    cb(null, body);
  });
};

function cleanupPhantomServer() {
  console.log('exiting');
  phantomServer.kill(function () {
    process.removeListener('SIGHUP', cleanupPhantomServer);
  });
}
process.on('SIGHUP', cleanupPhantomServer);

// Request immediately
module.exports(
{"width":600,"height":392,"js":"\n      context.font = '30px Impact';\n      context.fillStyle = '#FFFFFF';\n      context.fillText('hi', 50, 100);\n      cb(null);\n    "}
, function (err, body) {
  console.log('ERR', err);
  console.log('body', !!body);
});
<commit_msg>Exiting program super gracefully<commit_after>var assert = require('assert');
var path = require('path');
var spawn = require('child_process').spawn;

var Tempfile = require('temporary/lib/file');
var request = require('request');
var which = require('which');
var phantomLocation = which.sync('phantomjs');

// DEV: This is a horrible way to organize code. Nothing like this should be run at require time
var phantomServer = spawn(phantomLocation, [__dirname + '/phantomjs/compose.js'],  {customFds: [0, 1, 2]});

module.exports = function getImageInfo (params, cb) {
  assert(params.js, 'JS not provided');
  assert(params.width, 'Width not provided');
  assert(params.height, 'Height not provided');

  // TODO: Consider passing a function or vanilla JS

  // Stringify our argument for phantomjs
  var arg = JSON.stringify(params),
      encodedArg = encodeURIComponent(arg);

  assert(encodedArg.length < 4096, 'JavaScript is over 4096 characters when encoded');

  request({
    url: 'http://localhost:9090/',
    method: 'POST',
    headers: {
      // DEV: PhantomJS looks for Proper-Case headers, request is lower-case =(
      'Content-Length': encodedArg.length
    },
    body: encodedArg
  }, function handlePhantomResponse(err, res, body) {
    // If there was an error, callback with it
    if (err) {
      return cb(err);
    }

    // If there was a bad status code, callback with a good message
    if (res.statusCode < 200 || res.statusCode >= 300) {
      return cb(new Error('PhantomJS server responded with "' + res.statusCode + '" status code. ' + body));
    }

    // Otherwise, callback with the body
    cb(null, body);
  });
};

function cleanupPhantomServer(SIGNAL) {
  return function cleanupPhantomServerFn() {
    phantomServer.kill();
    process.removeListener(SIGNAL, cleanupPhantomServerFn);
    phantomServer.on('exit', function () {
      process.kill(process.pid, SIGNAL);
    });
  };
}
process.on('SIGINT', cleanupPhantomServer('SIGINT'));
process.on('SIGHUP', cleanupPhantomServer('SIGHUP'));
process.on('SIGUSR2', cleanupPhantomServer('SIGUSR2'));

// Request immediately
module.exports(
{"width":600,"height":392,"js":"\n      context.font = '30px Impact';\n      context.fillStyle = '#FFFFFF';\n      context.fillText('hi', 50, 100);\n      cb(null);\n    "}
, function (err, body) {
  console.log('ERR', err);
  console.log('body', !!body);
});
<|endoftext|>


<commit_before>/*! @license ©2014 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
/* A UnionIterator returns the results from a set of source iterators. */

var Iterator = require('./Iterator');

// Creates a new UnionIterator from the given sources
function UnionIterator(sources, options) {
  if (!(this instanceof UnionIterator))
    return new UnionIterator(sources, options);
  Iterator.call(this, options);

  // Construct a list of readable sources
  this._sources = [];
  this._sourceIndex = 0;
  if (sources && sources.length) {
    // Create event listeners
    var self = this;
    function fillBuffer()     { self._fillBuffer(); }
    function emitError(error) { self._error(error); }

    // Add all readable sources and listen to their events
    for (var i = 0, l = sources.length; i < l; i++) {
      var source = sources[i];
      if (source && !source.ended) {
        this._sources.push(source);
        source.on('readable', fillBuffer);
        source.on('end',      fillBuffer);
        source.on('error',    emitError);
      }
    }
  }
  // If there are no readable sources, end the iterator
  (this._sources.length === 0) && this._end();
}
Iterator.inherits(UnionIterator);

// Reads items from the sources in a round-robin way
UnionIterator.prototype._read = function () {
  var item = null, sources = this._sources, attempts = sources.length;
  // While no item has been found, attempt to read all sources once
  while (!item && attempts--) {
    var source = sources[this._sourceIndex];
    item = source.read();
    // Remove the current source if it has ended
    source.ended ? sources.splice(this._sourceIndex, 1) : this._sourceIndex++;
    (this._sourceIndex < sources.length) || (this._sourceIndex = 0);
  }
  // Push the item if one has been found
  (item !== null) && this._push(item);
  // End the iterator if no readable sources are left
  (this._sources.length === 0) && this._end();
};

module.exports = UnionIterator;
<commit_msg>Disallow recursive reads in UnionIterator.<commit_after>/*! @license ©2014 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
/* A UnionIterator returns the results from a set of source iterators. */

var Iterator = require('./Iterator');

// Creates a new UnionIterator from the given sources
function UnionIterator(sources, options) {
  if (!(this instanceof UnionIterator))
    return new UnionIterator(sources, options);
  Iterator.call(this, options);

  // Construct a list of readable sources
  this._sources = [];
  this._sourceIndex = 0;
  if (sources && sources.length) {
    // Create event listeners
    var self = this;
    function fillBuffer()     { self._fillBuffer(); }
    function emitError(error) { self._error(error); }

    // Add all readable sources and listen to their events
    for (var i = 0, l = sources.length; i < l; i++) {
      var source = sources[i];
      if (source && !source.ended) {
        this._sources.push(source);
        source.on('readable', fillBuffer);
        source.on('end',      fillBuffer);
        source.on('error',    emitError);
      }
    }
  }
  // If there are no readable sources, end the iterator
  (this._sources.length === 0) && this._end();
  this._reading = false;
}
Iterator.inherits(UnionIterator);

// Reads items from the sources in a round-robin way
UnionIterator.prototype._read = function () {
  if (this._reading) return;
  this._reading = true;

  var item = null, sources = this._sources, attempts = sources.length;
  // While no item has been found, attempt to read all sources once
  while (!item && attempts--) {
    var source = sources[this._sourceIndex];
    item = source.read();
    // Remove the current source if it has ended
    source.ended ? sources.splice(this._sourceIndex, 1) : this._sourceIndex++;
    (this._sourceIndex < sources.length) || (this._sourceIndex = 0);
  }
  // Push the item if one has been found
  (item !== null) && this._push(item);
  // End the iterator if no readable sources are left
  (this._sources.length === 0) && this._end();
  this._reading = false;
};

module.exports = UnionIterator;
<|endoftext|>


<commit_before>var marked = require('marked');
var highlight = require('highlight.js');
var diff = require('diff');

module.exports = function(source) {
  this.cacheable && this.cacheable();

  source = JSON.parse(source);
  var locals = source.locals;
  locals = JSON.stringify(Object.keys(source.locals || {}).map(function(key) {
    return [key, locals[key]];
  }));

  var content = doHighlight(source.__content);

  this.addDependency(source.extends);

  var parts = [
    'extends ' + source.extends,
    'append locals',
    '  - ' + locals + '.forEach(function(kv) { locals[kv[0]] = kv[1]});',
    'block ' + (source.block || 'main'),
    '  != ' + JSON.stringify(content),
  ];

  return parts.join('\n');
};

var renderer = new marked.Renderer();

renderer.heading = function(text, level) {
  var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');

  return '' +
    '<h' + level + '>' +
      text +
      '' +
        '' +
      '' +
    '</h' + level + '>';
};

function doHighlight(content) {
  var codeId = 0;
  var codeBlocks = {};

  var INCLUDE_RE = /^INCLUDE (\w+):(\d+)(?:-(\d+))?$/;

  var rendered = marked(content || '', {
    renderer: renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false,
    langPrefix: 'hljs ',
    highlight: function(code, lang) {
      if (!lang) return code;

      var blockName = codeId++;
      var parent;

      lang = lang.replace(/\[([^\]]+)\]/, function(_, n) {
        var nameParts = n.split(/\s*\<\-\s*/);
        blockName = nameParts[0];
        parent = nameParts[1];
        return '';
      });

      codeBlocks[blockName] = code;

      var id = 'code-' + blockName + '-';

      var highlighted = highlight.highlight(lang, code).value;
      if (!parent) return addLineNumbers(highlighted, id);

      var parentBlock = codeBlocks[parent];

      if (!parentBlock) {
        throw new Error('Missing parent code block ' + JSON.stringify(parent) + ' in ' + JSON.stringify(blockName));
      }

      var i = 0;

      var out = diff
        .diffLines(parentBlock, code)
        .map(function(line) {
          var className;

          if (line.added) className = 'hljs-diff-line-added';
          if (line.removed) className = 'hljs-diff-line-removed';
          if (!className) className = 'hljs-diff-line-unchanged';

          var value = highlight.highlight(lang, line.value).value;

          return wrapLines(value, function(v) {
            if (!v) return '';
            return '' + v + '';
          });
        })
        .join('');

      return out;
    }
  });

  return rendered;
}

function addLineNumbers(code, id) {
  return wrapLines(code, function(line, i) {
    i = i + 1;
    var lineLink = id + i;
    return '' + line + '';
  });
}

function wrapLines(code, cb) {
  return code
    .split('\n')
    .map(function(line, i) {
      return cb(line, i);
    })
    .join('\n');
}
<commit_msg>wrap new line in diff span<commit_after>var marked = require('marked');
var highlight = require('highlight.js');
var diff = require('diff');

module.exports = function(source) {
  this.cacheable && this.cacheable();

  source = JSON.parse(source);
  var locals = source.locals;
  locals = JSON.stringify(Object.keys(source.locals || {}).map(function(key) {
    return [key, locals[key]];
  }));

  var content = doHighlight(source.__content);

  this.addDependency(source.extends);

  var parts = [
    'extends ' + source.extends,
    'append locals',
    '  - ' + locals + '.forEach(function(kv) { locals[kv[0]] = kv[1]});',
    'block ' + (source.block || 'main'),
    '  != ' + JSON.stringify(content),
  ];

  return parts.join('\n');
};

var renderer = new marked.Renderer();

renderer.heading = function(text, level) {
  var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');

  return '' +
    '<h' + level + '>' +
      text +
      '' +
        '' +
      '' +
    '</h' + level + '>';
};

function doHighlight(content) {
  var codeId = 0;
  var codeBlocks = {};

  var INCLUDE_RE = /^INCLUDE (\w+):(\d+)(?:-(\d+))?$/;

  var rendered = marked(content || '', {
    renderer: renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false,
    langPrefix: 'hljs ',
    highlight: function(code, lang) {
      if (!lang) return code;

      var blockName = codeId++;
      var parent;

      lang = lang.replace(/\[([^\]]+)\]/, function(_, n) {
        var nameParts = n.split(/\s*\<\-\s*/);
        blockName = nameParts[0];
        parent = nameParts[1];
        return '';
      });

      codeBlocks[blockName] = code;

      var id = 'code-' + blockName + '-';

      var highlighted = highlight.highlight(lang, code).value;
      if (!parent) return addLineNumbers(highlighted, id);

      var parentBlock = codeBlocks[parent];

      if (!parentBlock) {
        throw new Error('Missing parent code block ' + JSON.stringify(parent) + ' in ' + JSON.stringify(blockName));
      }

      var i = 0;

      var out = diff
        .diffLines(parentBlock, code)
        .map(function(line) {
          var className;

          if (line.added) className = 'hljs-diff-line-added';
          if (line.removed) className = 'hljs-diff-line-removed';
          if (!className) className = 'hljs-diff-line-unchanged';

          var value = highlight.highlight(lang, line.value).value;

          return wrapLines(value, function(v, it, lines) {
            var nl = it === lines.length - 1 ? '' : '\n';
            if (!v) return nl;
            return '' + v + nl + '';
          }, false);
        })
        .join('');

      return out;
    }
  });

  return rendered;
}

function addLineNumbers(code, id) {
  return wrapLines(code, function(line, i) {
    i = i + 1;
    var lineLink = id + i;
    return '' + line + '';
  });
}

function wrapLines(code, cb, addNewLine) {
  if (typeof addNewLine === 'undefined') addNewLine = true;
  var lines = code.split('\n');

  return lines
    .map(function(line, i) {
      return cb(line, i, lines);
    })
    .join(addNewLine ? '\n' : '');
}
<|endoftext|>


<commit_before>//import * as dl from 'deeplearn';
//import {MnistData} from './data';

// Hyperparameters.
const LEARNING_RATE = .05;
const BATCH_SIZE = 64;
const TRAIN_STEPS = 100;

// Data constants.
const IMAGE_SIZE = 784;
const LABELS_SIZE = 10;

//const math = dl.ENV.math;

const optimizer = new dl.SGDOptimizer(LEARNING_RATE);

// Set up the model and loss function.

var weights = dl.variable(dl.Array2D.randNormal([IMAGE_SIZE, LABELS_SIZE], 0, 1 / Math.sqrt(IMAGE_SIZE), 'float32'));

var model = function (xs) {
    return math.matMul(xs, weights);
};

var loss = function (labels, ys) {
    return math.mean(math.softmaxCrossEntropyWithLogits(labels, ys));
};

// Train the model.
async function train(data, log) {
  const returnCost = true;
  for (let i = 0; i < TRAIN_STEPS; i++) {
    const cost = dl.optimizer.minimize(  () => {
      const batch = data.nextTrainBatch(BATCH_SIZE);

      return loss(batch.labels, model(batch.xs));
    }, returnCost);

    log("loss[" + i + "]: " + cost.dataSync());

    await dl.util.nextFrame();
  }
}

// Tests the model on a set
async function test(data) {}

// Predict the digit number from a batch of input images.
function predict(x) {
  const pred = math.scope(() => {
    const axis = 1;
    return math.argMax(model(x), axis);
  });
  return Array.from(pred.dataSync());
}

// Given a logits or label vector, return the class indices.
function classesFromLabel(y){
  const axis = 1;
  const pred = math.argMax(y, axis);

  return Array.from(pred.dataSync());
}
<commit_msg>Update model.js<commit_after>//import * as dl from 'deeplearn';
//import {MnistData} from './data';

// Hyperparameters.
const LEARNING_RATE = .05;
const BATCH_SIZE = 64;
const TRAIN_STEPS = 100;

// Data constants.
const IMAGE_SIZE = 784;
const LABELS_SIZE = 10;

const mathModel2 = dl.ENV.math;

const optimizer = new dl.SGDOptimizer(LEARNING_RATE);

// Set up the model and loss function.

const weights = dl.variable(dl.Array2D.randNormal([IMAGE_SIZE, LABELS_SIZE], 0, 1 / Math.sqrt(IMAGE_SIZE), 'float32'));

// Train the model.
const model = function (xs) {
  return mathModel2.matMul(xs, weights);
};

const loss = function (labels, ys) {
  return mathModel2.mean(math.softmaxCrossEntropyWithLogits(labels, ys));
};

// Train the model.
async function train(data)  {
  const returnCost = true;
  for (let i = 0; i < TRAIN_STEPS; i++) {
    const cost = optimizer.minimize( function () {
      const batch = data.nextTrainBatch(BATCH_SIZE);

      return loss(batch.labels, model(batch.xs));
    }, returnCost);

    console.log(`loss[${i}]: ${cost.dataSync()}`);

    await dl.util.nextFrame();
  }
}

// Tests the model on a set
async function test(data) {}

// Predict the digit number from a batch of input images.
function predict(x) {
  const pred = mathModel2.scope(() => {
    const axis = 1;
    return mathModel2.argMax(model(x), axis);
  });
  return Array.from(pred.dataSync());
}

// Given a logits or label vector, return the class indices.
function classesFromLabel(y){
  const axis = 1;
  const pred = mathModel2.argMax(y, axis);

  return Array.from(pred.dataSync());
}
<|endoftext|>


<commit_before>var api = {
    get: (request, response, next) => {
        var query = {
            sql: 'UPDATE TodoItem SET complete=@completed',
            parameters: [
                { name: 'completed', value: request.params.completed }
            ]
        };

        request.azureMobile.data.execute(query)
        .then(function (results) {
            response.json(results);
        });
    }
};

module.exports = api;
<commit_msg>Added example of executing a stored procedure<commit_after>var api = {
    // an example of executing a SQL statement directly
    get: (request, response, next) => {
        var query = {
            sql: 'UPDATE TodoItem SET complete = @completed',
            parameters: [
                { name: 'completed', value: request.query.completed }
            ]
        };

        request.azureMobile.data.execute(query)
        .then(function (results) {
            response.json(results);
        });
    },
    // an example of executing a stored procedure
    post: (request, response, next) => {
        var query = {
            sql: 'EXEC completeAllStoredProcedure @completed',
            parameters: [
                { name: 'completed', value: request.query.completed }
            ]
        };

        request.azureMobile.data.execute(query)
        .then(function (results) {
            response.json(results);
        });
    }
};

module.exports = api;
<|endoftext|>


<commit_before>const moment = require('moment')
const bluebird = require('bluebird')
const cozyClient = require('./cozyclient')
const DOCTYPE = 'io.cozy.bank.operations'
const log = require('./logger')

module.exports = (entries, doctype, options = {}) => {
  if (typeof options.identifiers === 'string') {
    options.identifiers = [options.identifiers.toLowerCase()]
  } else if (Array.isArray(options.identifiers)) {
    options.identifiers = options.identifiers.map((id) => (id.toLowerCase()))
  } else {
    throw new Error('linkBankOperations cannot be called without "identifiers" option')
  }

  Object.assign(options, {
    amountDelta: 0.001,
    dateDelta: 15
  })

  options.minDateDelta = options.minDateDelta || options.dateDelta
  options.maxDateDelta = options.maxDateDelta || options.dateDelta

  // for each entry try to get a corresponding operation
  return bluebird.each(entries, entry => {
    let date = new Date(entry.paidDate || entry.date)
    let startDate = moment(date).subtract(options.minDateDelta, 'days')
    let endDate = moment(date).add(options.maxDateDelta, 'days')

    // get the list of operation corresponding to the date interval arount the date of the entry
    let startkey = `${startDate.format('YYYY-MM-DDT00:00:00.000')}Z`
    let endkey = `${endDate.format('YYYY-MM-DDT00:00:00.000')}Z`
    return cozyClient.data.defineIndex(DOCTYPE, ['date'])
    .then(index => cozyClient.data.query(index, { selector: {
      date: {
        '$gt': startkey,
        '$lt': endkey
      }}
    }))
    .then(operations => {
      // find the operations with the expected identifier
      let operationToLink = null
      let candidateOperationsForLink = []

      let amount = Math.abs(parseFloat(entry.amount))
      // By default, an entry is an expense. If it is not, it should be
      // declared as a refund: isRefund=true.
      if (entry.isRefund === true) amount *= -1

      let minAmountDelta = Infinity

      for (let operation of operations) {
        let opAmount = Math.abs(operation.amount)

        // By default, an entry is an expense. If it is not, it should be
        // declared as a refund: isRefund=true.
        if (entry.isRefund === true) opAmount *= -1

        let amountDelta = Math.abs(opAmount - amount)

        // Select the operation to link based on the minimal amount
        // difference to the expected one and if the label matches one
        // of the possible labels (identifier)
        // If unsafe matching is enabled, also find all operations for which the entry could ba a part of
        for (let identifier of options.identifiers) {
          if (operation.label.toLowerCase().indexOf(identifier) >= 0 &&
            amountDelta <= options.amountDelta &&
            amountDelta <= minAmountDelta) {
            operationToLink = operation
            minAmountDelta = amountDelta
            break
          } else if (operation.label.toLowerCase().indexOf(identifier) >= 0 &&  // label must match
                  !operation.parent &&                                       // not a child operation itself
                  amountDelta > 0 &&                                         // op amount is smaller than entry amount
                  ((entry.isRefund && operation.amount > 0) ||               // if entry is refund, op is refund
                  (!entry.isRefund && operation.amount < 0)) &&              // if entry is expense, op is expense
                  this.allowUnsafeLinks) {
            candidateOperationsForLink.push(operation)
          }
        }
      }

      if (operationToLink !== null && entry._id) {
        log('debug', operationToLink, 'There is an operation to link')
        let link = `${doctype}:${entry._id}`
        if (operationToLink.bill === link) return Promise.resolve()
        return cozyClient.data.updateAttributes(DOCTYPE, operationToLink._id, { bill: link })
      }
      return Promise.resolve()
    })
  })
}
<commit_msg>fix: make linkBankOperation work<commit_after>const moment = require('moment')
const bluebird = require('bluebird')
const cozyClient = require('./cozyclient')
const DOCTYPE = 'io.cozy.bank.operations'
const log = require('./logger')
const debug = require('debug')('linkBankOperation')

module.exports = (entries, doctype, options = {}) => {
  if (typeof options.identifiers === 'string') {
    options.identifiers = [options.identifiers.toLowerCase()]
  } else if (Array.isArray(options.identifiers)) {
    options.identifiers = options.identifiers.map((id) => (id.toLowerCase()))
  } else {
    throw new Error('linkBankOperations cannot be called without "identifiers" option')
  }

  Object.assign(options, {
    amountDelta: 0.001,
    dateDelta: 15
  })

  options.minDateDelta = options.minDateDelta || options.dateDelta
  options.maxDateDelta = options.maxDateDelta || options.dateDelta

  // for each entry try to get a corresponding operation
  return bluebird.each(entries, entry => {
    let date = new Date(entry.paidDate || entry.date)
    let startDate = moment(date).subtract(options.minDateDelta, 'days')
    let endDate = moment(date).add(options.maxDateDelta, 'days')

    // get the list of operation corresponding to the date interval arount the date of the entry
    let startkey = `${startDate.format('YYYY-MM-DDT00:00:00.000')}Z`
    let endkey = `${endDate.format('YYYY-MM-DDT00:00:00.000')}Z`
    return cozyClient.data.defineIndex(DOCTYPE, ['date'])
    .then(index => cozyClient.data.query(index, { selector: {
      date: {
        '$gt': startkey,
        '$lt': endkey
      }}
    }))
    .then(operations => {
      debug(operations.length, 'Number of operations to check')
      // find the operations with the expected identifier
      let operationToLink = null
      let candidateOperationsForLink = []

      let amount = Math.abs(parseFloat(entry.amount))
      // By default, an entry is an expense. If it is not, it should be
      // declared as a refund: isRefund=true.
      if (entry.isRefund === true) amount *= -1

      let minAmountDelta = Infinity

      for (let operation of operations) {
        let opAmount = Math.abs(operation.amount)

        // By default, an entry is an expense. If it is not, it should be
        // declared as a refund: isRefund=true.
        if (entry.isRefund === true) opAmount *= -1

        let amountDelta = Math.abs(opAmount - amount)

        // Select the operation to link based on the minimal amount
        // difference to the expected one and if the label matches one
        // of the possible labels (identifier)
        // If unsafe matching is enabled, also find all operations for which the entry could ba a part of
        for (let identifier of options.identifiers) {
          if (operation.label.toLowerCase().indexOf(identifier) >= 0 &&
            amountDelta <= options.amountDelta &&
            amountDelta <= minAmountDelta) {
            operationToLink = operation
            minAmountDelta = amountDelta
            break
          } else if (operation.label.toLowerCase().indexOf(identifier) >= 0 &&  // label must match
                  !operation.parent &&                                       // not a child operation itself
                  amountDelta > 0 &&                                         // op amount is smaller than entry amount
                  ((entry.isRefund && operation.amount > 0) ||               // if entry is refund, op is refund
                  (!entry.isRefund && operation.amount < 0)) &&              // if entry is expense, op is expense
                  this.allowUnsafeLinks) {
            candidateOperationsForLink.push(operation)
          }
        }
      }

      if (operationToLink !== null && entry.file) {
        log('debug', operationToLink, 'There is an operation to link')
        let link = `io.cozy.files:${entry.file}`
        if (operationToLink.bill === link) return Promise.resolve()
        return cozyClient.data.updateAttributes(DOCTYPE, operationToLink._id, { bill: link })
      }
      return Promise.resolve()
    })
  })
}
<|endoftext|>