<commit_before>import window from 'global';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import memoize from 'memoizerific';
import copy from 'copy-to-clipboard';
import { styled } from '@storybook/theming';
import { SET_CURRENT_STORY } from '@storybook/core-events';
import addons, { types } from '@storybook/addons';
import merge from '@storybook/api/dist/lib/merge';
import { Icons, IconButton, TabButton, TabBar, Separator } from '@storybook/components';

import { Helmet } from 'react-helmet-async';

import { Toolbar } from './toolbar';

import * as S from './components';

import { ZoomProvider, ZoomConsumer, Zoom } from './zoom';

import { IFrame } from './iframe';

const DesktopOnly = styled.span({
  // Hides full screen icon at mobile breakpoint defined in app.js
  '@media (max-width: 599px)': {
    display: 'none',
  },
});
const stringifyQueryParams = queryParams =>
  Object.entries(queryParams).reduce((acc, [k, v]) => {
    return `${acc}&${k}=${v}`;
  }, '');

const renderIframe = (storyId, viewMode, id, baseUrl, scale, queryParams) => (
  <IFrame
    key="iframe"
    id="storybook-preview-iframe"
    title={id || 'preview'}
    src={`${baseUrl}?id=${storyId}&viewMode=${viewMode}${stringifyQueryParams(queryParams)}`}
    allowFullScreen
    scale={scale}
  />
);

const getElementList = memoize(10)((getFn, type, base) => base.concat(Object.values(getFn(type))));

const ActualPreview = ({
  wrappers,
  viewMode,
  id,
  storyId,
  active,
  baseUrl,
  scale,
  queryParams,
  customCanvas,
}) => {
  const data = [storyId, viewMode, id, baseUrl, scale, queryParams];
  const base = customCanvas ? customCanvas(...data) : renderIframe(...data);
  return wrappers.reduceRight(
    (acc, wrapper, index) => wrapper.render({ index, children: acc, id, storyId, active }),
    base
  );
};

const IframeWrapper = styled.div(({ theme }) => ({
  position: 'absolute',
  top: 0,
  left: 0,
  bottom: 0,
  right: 0,
  width: '100%',
  height: '100%',
  background: theme.background.content,
}));

const defaultWrappers = [
  {
    render: p => (
      <IframeWrapper id="storybook-preview-wrapper" hidden={!p.active}>
        {p.children}
      </IframeWrapper>
    ),
  },
];

const getTools = memoize(10)(
  (getElements, queryParams, panels, api, options, storyId, viewMode, location, path, baseUrl) => {
    const tools = getElementList(getElements, types.TOOL, [
      panels.filter(p => !p.hidden).length > 1
        ? {
            render: () => (
              <Fragment>
                <TabBar key="tabs" scroll={false}>
                  {panels
                    .filter(p => !p.hidden)
                    .map((t, index) => {
                      const to = t.route({ storyId, viewMode, path, location });
                      const isActive = path === to;
                      return (
                        <S.UnstyledLink key={t.id || `l${index}`} to={to}>
                          <TabButton disabled={t.disabled} active={isActive}>
                            {t.title}
                          </TabButton>
                        </S.UnstyledLink>
                      );
                    })}
                </TabBar>
                <Separator />
              </Fragment>
            ),
          }
        : null,
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <Fragment>
            <ZoomConsumer>
              {({ set, value }) => (
                <Zoom key="zoom" current={value} set={v => set(value * v)} reset={() => set(1)} />
              )}
            </ZoomConsumer>
            <Separator />
          </Fragment>
        ),
      },
    ]);

    const extraTools = getElementList(getElements, types.TOOLEXTRA, [
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <DesktopOnly>
            <IconButton
              key="full"
              onClick={api.toggleFullscreen}
              title={options.isFullscreen ? 'Exit full screen' : 'Go full screen'}
            >
              <Icons icon={options.isFullscreen ? 'close' : 'expand'} />
            </IconButton>
          </DesktopOnly>
        ),
      },
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <IconButton
            key="opener"
            onClick={() =>
              window.open(`${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`)
            }
            title="Open canvas in new tab"
          >
            <Icons icon="share" />
          </IconButton>
        ),
      },
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <IconButton
            key="copy"
            onClick={() =>
              copy(
                `${window.location.origin}${
                  window.location.pathname
                }${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`
              )
            }
            title="Copy canvas link"
          >
            <Icons icon="copy" />
          </IconButton>
        ),
      },
    ]);

    const filter = item =>
      item && (!item.match || item.match({ storyId, viewMode, location, path }));

    const displayItems = list =>
      list.reduce(
        (acc, item, index) =>
          item ? (
            <Fragment key={item.id || item.key || `f-${index}`}>
              {acc}
              {item.render() || item}
            </Fragment>
          ) : (
            acc
          ),
        null
      );

    const left = displayItems(tools.filter(filter));
    const right = displayItems(extraTools.filter(filter));

    return { left, right };
  }
);

const getDocumentTitle = description => {
  return description ? `${description} ⋅ Storybook` : 'Storybook';
};

class Preview extends Component {
  shouldComponentUpdate({ storyId, viewMode, options, queryParams, parameters }) {
    const { props } = this;

    return (
      options.isFullscreen !== props.options.isFullscreen ||
      options.isToolshown !== props.options.isToolshown ||
      viewMode !== props.viewMode ||
      storyId !== props.storyId ||
      queryParams !== props.queryParams ||
      parameters !== props.parameters
    );
  }

  componentDidUpdate(prevProps) {
    const { api, storyId, viewMode } = this.props;
    const { storyId: prevStoryId, viewMode: prevViewMode } = prevProps;
    if ((storyId && storyId !== prevStoryId) || (viewMode && viewMode !== prevViewMode)) {
      api.emit(SET_CURRENT_STORY, { storyId, viewMode });
    }
  }

  render() {
    const {
      id,
      path,
      location,
      viewMode,
      storyId,
      queryParams,
      getElements,
      api,
      customCanvas,
      options,
      description,
      baseUrl,
      parameters,
    } = this.props;
    const toolbarHeight = options.isToolshown ? 40 : 0;
    const wrappers = getElementList(getElements, types.PREVIEW, defaultWrappers);
    let panels = getElementList(getElements, types.TAB, [
      {
        route: p => `/story/${p.storyId}`,
        match: p => p.viewMode && p.viewMode.match(/^(story|docs)$/),
        render: p => (
          <ZoomConsumer>
            {({ value }) => {
              const props = {
                viewMode,
                active: p.active,
                wrappers,
                id,
                storyId,
                baseUrl,
                queryParams,
                scale: value,
                customCanvas,
              };

              return <ActualPreview {...props} />;
            }}
          </ZoomConsumer>
        ),
        title: 'Canvas',
        id: 'canvas',
      },
    ]);
    const { previewTabs } = addons.getConfig();
    const parametersTabs = parameters ? parameters.previewTabs : undefined;
    if (previewTabs || parametersTabs) {
      // deep merge global and local settings
      const tabs = merge(previewTabs, parametersTabs);
      const arrTabs = Object.keys(tabs).map((key, index) => ({
        index,
        ...(typeof tabs[key] === 'string' ? { title: tabs[key] } : tabs[key]),
        id: key,
      }));
      panels = panels
        .filter(panel => {
          const t = arrTabs.find(tab => tab.id === panel.id);
          return t === undefined || t.id === 'canvas' || !t.hidden;
        })
        .map((panel, index) => ({ ...panel, index }))
        .sort((p1, p2) => {
          const tab_1 = arrTabs.find(tab => tab.id === p1.id);
          const index_1 = tab_1 ? tab_1.index : arrTabs.length + p1.index;
          const tab_2 = arrTabs.find(tab => tab.id === p2.id);
          const index_2 = tab_2 ? tab_2.index : arrTabs.length + p2.index;
          return index_1 - index_2;
        })
        .map(panel => {
          const t = arrTabs.find(tab => tab.id === panel.id);
          if (t) {
            return {
              ...panel,
              title: t.title || panel.title,
              disabled: t.disabled,
              hidden: t.hidden,
            };
          }
          return panel;
        });
    }
    const { left, right } = getTools(
      getElements,
      queryParams,
      panels,
      api,
      options,
      storyId,
      viewMode,
      location,
      path,
      baseUrl
    );

    return (
      <ZoomProvider>
        <Fragment>
          {id === 'main' && (
            <Helmet key="description">
              <title>{getDocumentTitle(description)}</title>
            </Helmet>
          )}
          <Toolbar key="toolbar" shown={options.isToolshown} border>
            <Fragment key="left">{left}</Fragment>
            <Fragment key="right">{right}</Fragment>
          </Toolbar>
          <S.FrameWrap key="frame" offset={toolbarHeight}>
            {panels.map(p => (
              <Fragment key={p.id || p.key}>
                {p.render({ active: p.match({ storyId, viewMode, location, path }) })}
              </Fragment>
            ))}
          </S.FrameWrap>
        </Fragment>
      </ZoomProvider>
    );
  }
}
Preview.propTypes = {
  id: PropTypes.string.isRequired,
  description: PropTypes.string,
  customCanvas: PropTypes.func,
  api: PropTypes.shape({
    on: PropTypes.func,
    off: PropTypes.func,
    emit: PropTypes.func,
    toggleFullscreen: PropTypes.func,
  }).isRequired,
  storyId: PropTypes.string,
  path: PropTypes.string,
  viewMode: PropTypes.string,
  location: PropTypes.shape({}).isRequired,
  getElements: PropTypes.func.isRequired,
  queryParams: PropTypes.shape({}).isRequired,
  options: PropTypes.shape({
    isFullscreen: PropTypes.bool,
    isToolshown: PropTypes.bool,
  }).isRequired,
  baseUrl: PropTypes.string,
  parameters: PropTypes.shape({
    previewTabs: PropTypes.shape({
      title: PropTypes.string,
      hidden: PropTypes.string,
      disabled: PropTypes.string,
    }),
  }),
};

Preview.defaultProps = {
  viewMode: undefined,
  storyId: undefined,
  path: undefined,
  description: undefined,
  baseUrl: 'iframe.html',
  customCanvas: undefined,
  parameters: undefined,
};

export { Preview };
<commit_msg>hide toolbar if no panels and no tools are present<commit_after>import window from 'global';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import memoize from 'memoizerific';
import copy from 'copy-to-clipboard';
import { styled } from '@storybook/theming';
import { SET_CURRENT_STORY } from '@storybook/core-events';
import addons, { types } from '@storybook/addons';
import merge from '@storybook/api/dist/lib/merge';
import { Icons, IconButton, TabButton, TabBar, Separator } from '@storybook/components';

import { Helmet } from 'react-helmet-async';

import { Toolbar } from './toolbar';

import * as S from './components';

import { ZoomProvider, ZoomConsumer, Zoom } from './zoom';

import { IFrame } from './iframe';

const DesktopOnly = styled.span({
  // Hides full screen icon at mobile breakpoint defined in app.js
  '@media (max-width: 599px)': {
    display: 'none',
  },
});
const stringifyQueryParams = queryParams =>
  Object.entries(queryParams).reduce((acc, [k, v]) => {
    return `${acc}&${k}=${v}`;
  }, '');

const renderIframe = (storyId, viewMode, id, baseUrl, scale, queryParams) => (
  <IFrame
    key="iframe"
    id="storybook-preview-iframe"
    title={id || 'preview'}
    src={`${baseUrl}?id=${storyId}&viewMode=${viewMode}${stringifyQueryParams(queryParams)}`}
    allowFullScreen
    scale={scale}
  />
);

const getElementList = memoize(10)((getFn, type, base) => base.concat(Object.values(getFn(type))));

const ActualPreview = ({
  wrappers,
  viewMode,
  id,
  storyId,
  active,
  baseUrl,
  scale,
  queryParams,
  customCanvas,
}) => {
  const data = [storyId, viewMode, id, baseUrl, scale, queryParams];
  const base = customCanvas ? customCanvas(...data) : renderIframe(...data);
  return wrappers.reduceRight(
    (acc, wrapper, index) => wrapper.render({ index, children: acc, id, storyId, active }),
    base
  );
};

const IframeWrapper = styled.div(({ theme }) => ({
  position: 'absolute',
  top: 0,
  left: 0,
  bottom: 0,
  right: 0,
  width: '100%',
  height: '100%',
  background: theme.background.content,
}));

const defaultWrappers = [
  {
    render: p => (
      <IframeWrapper id="storybook-preview-wrapper" hidden={!p.active}>
        {p.children}
      </IframeWrapper>
    ),
  },
];

const getTools = memoize(10)(
  (getElements, queryParams, panels, api, options, storyId, viewMode, location, path, baseUrl) => {
    const tools = getElementList(getElements, types.TOOL, [
      panels.filter(p => !p.hidden).length > 1
        ? {
            render: () => (
              <Fragment>
                <TabBar key="tabs" scroll={false}>
                  {panels
                    .filter(p => !p.hidden)
                    .map((t, index) => {
                      const to = t.route({ storyId, viewMode, path, location });
                      const isActive = path === to;
                      return (
                        <S.UnstyledLink key={t.id || `l${index}`} to={to}>
                          <TabButton disabled={t.disabled} active={isActive}>
                            {t.title}
                          </TabButton>
                        </S.UnstyledLink>
                      );
                    })}
                </TabBar>
                <Separator />
              </Fragment>
            ),
          }
        : null,
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <Fragment>
            <ZoomConsumer>
              {({ set, value }) => (
                <Zoom key="zoom" current={value} set={v => set(value * v)} reset={() => set(1)} />
              )}
            </ZoomConsumer>
            <Separator />
          </Fragment>
        ),
      },
    ]);

    const extraTools = getElementList(getElements, types.TOOLEXTRA, [
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <DesktopOnly>
            <IconButton
              key="full"
              onClick={api.toggleFullscreen}
              title={options.isFullscreen ? 'Exit full screen' : 'Go full screen'}
            >
              <Icons icon={options.isFullscreen ? 'close' : 'expand'} />
            </IconButton>
          </DesktopOnly>
        ),
      },
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <IconButton
            key="opener"
            onClick={() =>
              window.open(`${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`)
            }
            title="Open canvas in new tab"
          >
            <Icons icon="share" />
          </IconButton>
        ),
      },
      {
        match: p => p.viewMode === 'story',
        render: () => (
          <IconButton
            key="copy"
            onClick={() =>
              copy(
                `${window.location.origin}${
                  window.location.pathname
                }${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`
              )
            }
            title="Copy canvas link"
          >
            <Icons icon="copy" />
          </IconButton>
        ),
      },
    ]);

    const filter = item =>
      item && (!item.match || item.match({ storyId, viewMode, location, path }));

    const displayItems = list =>
      list.reduce(
        (acc, item, index) =>
          item ? (
            <Fragment key={item.id || item.key || `f-${index}`}>
              {acc}
              {item.render() || item}
            </Fragment>
          ) : (
            acc
          ),
        null
      );

    const left = displayItems(tools.filter(filter));
    const right = displayItems(extraTools.filter(filter));

    return { left, right };
  }
);

const getDocumentTitle = description => {
  return description ? `${description} ⋅ Storybook` : 'Storybook';
};

class Preview extends Component {
  shouldComponentUpdate({ storyId, viewMode, options, queryParams, parameters }) {
    const { props } = this;

    return (
      options.isFullscreen !== props.options.isFullscreen ||
      options.isToolshown !== props.options.isToolshown ||
      viewMode !== props.viewMode ||
      storyId !== props.storyId ||
      queryParams !== props.queryParams ||
      parameters !== props.parameters
    );
  }

  componentDidUpdate(prevProps) {
    const { api, storyId, viewMode } = this.props;
    const { storyId: prevStoryId, viewMode: prevViewMode } = prevProps;
    if ((storyId && storyId !== prevStoryId) || (viewMode && viewMode !== prevViewMode)) {
      api.emit(SET_CURRENT_STORY, { storyId, viewMode });
    }
  }

  render() {
    const {
      id,
      path,
      location,
      viewMode,
      storyId,
      queryParams,
      getElements,
      api,
      customCanvas,
      options,
      description,
      baseUrl,
      parameters,
    } = this.props;
    const toolbarHeight = options.isToolshown ? 40 : 0;
    const wrappers = getElementList(getElements, types.PREVIEW, defaultWrappers);
    let panels = getElementList(getElements, types.TAB, [
      {
        route: p => `/story/${p.storyId}`,
        match: p => p.viewMode && p.viewMode.match(/^(story|docs)$/),
        render: p => (
          <ZoomConsumer>
            {({ value }) => {
              const props = {
                viewMode,
                active: p.active,
                wrappers,
                id,
                storyId,
                baseUrl,
                queryParams,
                scale: value,
                customCanvas,
              };

              return <ActualPreview {...props} />;
            }}
          </ZoomConsumer>
        ),
        title: 'Canvas',
        id: 'canvas',
      },
    ]);
    const { previewTabs } = addons.getConfig();
    const parametersTabs = parameters ? parameters.previewTabs : undefined;
    if (previewTabs || parametersTabs) {
      // deep merge global and local settings
      const tabs = merge(previewTabs, parametersTabs);
      const arrTabs = Object.keys(tabs).map((key, index) => ({
        index,
        ...(typeof tabs[key] === 'string' ? { title: tabs[key] } : tabs[key]),
        id: key,
      }));
      panels = panels
        .filter(panel => {
          const t = arrTabs.find(tab => tab.id === panel.id);
          return t === undefined || t.id === 'canvas' || !t.hidden;
        })
        .map((panel, index) => ({ ...panel, index }))
        .sort((p1, p2) => {
          const tab_1 = arrTabs.find(tab => tab.id === p1.id);
          const index_1 = tab_1 ? tab_1.index : arrTabs.length + p1.index;
          const tab_2 = arrTabs.find(tab => tab.id === p2.id);
          const index_2 = tab_2 ? tab_2.index : arrTabs.length + p2.index;
          return index_1 - index_2;
        })
        .map(panel => {
          const t = arrTabs.find(tab => tab.id === panel.id);
          if (t) {
            return {
              ...panel,
              title: t.title || panel.title,
              disabled: t.disabled,
              hidden: t.hidden,
            };
          }
          return panel;
        });
    }
    const { left, right } = getTools(
      getElements,
      queryParams,
      panels,
      api,
      options,
      storyId,
      viewMode,
      location,
      path,
      baseUrl
    );

    return (
      <ZoomProvider>
        <Fragment>
          {id === 'main' && (
            <Helmet key="description">
              <title>{getDocumentTitle(description)}</title>
            </Helmet>
          )}
          {(left || right) && (
            <Toolbar key="toolbar" shown={options.isToolshown} border>
              <Fragment key="left">{left}</Fragment>
              <Fragment key="right">{right}</Fragment>
            </Toolbar>
          )}
          <S.FrameWrap key="frame" offset={toolbarHeight}>
            {panels.map(p => (
              <Fragment key={p.id || p.key}>
                {p.render({ active: p.match({ storyId, viewMode, location, path }) })}
              </Fragment>
            ))}
          </S.FrameWrap>
        </Fragment>
      </ZoomProvider>
    );
  }
}
Preview.propTypes = {
  id: PropTypes.string.isRequired,
  description: PropTypes.string,
  customCanvas: PropTypes.func,
  api: PropTypes.shape({
    on: PropTypes.func,
    off: PropTypes.func,
    emit: PropTypes.func,
    toggleFullscreen: PropTypes.func,
  }).isRequired,
  storyId: PropTypes.string,
  path: PropTypes.string,
  viewMode: PropTypes.string,
  location: PropTypes.shape({}).isRequired,
  getElements: PropTypes.func.isRequired,
  queryParams: PropTypes.shape({}).isRequired,
  options: PropTypes.shape({
    isFullscreen: PropTypes.bool,
    isToolshown: PropTypes.bool,
  }).isRequired,
  baseUrl: PropTypes.string,
  parameters: PropTypes.shape({
    previewTabs: PropTypes.shape({
      title: PropTypes.string,
      hidden: PropTypes.string,
      disabled: PropTypes.string,
    }),
  }),
};

Preview.defaultProps = {
  viewMode: undefined,
  storyId: undefined,
  path: undefined,
  description: undefined,
  baseUrl: 'iframe.html',
  customCanvas: undefined,
  parameters: undefined,
};

export { Preview };
<|endoftext|>


<commit_before>'use strict';

var queue = require('queue-async');
var _ = require('underscore');

function LayerMetadata(layerMetadataIterator) {
    this.layerMetadataIterator = layerMetadataIterator;
}

LayerMetadata.prototype.getMetadata = function (rendererCache, params, mapConfig, callback) {
    var self = this;
    var metadata = [];

    var metaQueue = queue(mapConfig.getLayers().length);

    mapConfig.getLayers().forEach(function(layer, layerId) {
        var layerType = mapConfig.layerType(layerId);

        for (var i = 0; i < self.layerMetadataIterator.length; i++) {
            if (self.layerMetadataIterator[i].is(layerType)) {
                var getMetadata = self.layerMetadataIterator[i].getMetadata.bind(self.layerMetadataIterator[i]);
                metaQueue.defer(getMetadata, mapConfig, layer, layerId, params, rendererCache);
                break;
            }
        }
    });

    metaQueue.awaitAll(function (err, results) {
        if (err) {
            return callback(err);
        }

        if (!results) {
            return callback(null, null);
        }

        mapConfig.getLayers().forEach(function(layer, layerId) {
            var layerType = mapConfig.layerType(layerId);

            metadata[layerId] = {
                type: layerType,
                meta: results[layerId]
            };

            var widgets = mapConfig.getWidgets(layerId);

            if (_.size(widgets)) {
                metadata[layerId].widgets = widgets;
            }

            var cartocss = mapConfig.getCartoCSS(layerId);

            if (_.size(cartocss)) {
                metadata[layerId].meta.cartocss = cartocss;
            }
        });

        return callback(err, metadata);
    });

};

module.exports = LayerMetadata;
<commit_msg>Removed underscore function that it doesn't make sense to use.<commit_after>'use strict';

var queue = require('queue-async');
var _ = require('underscore');

function LayerMetadata(layerMetadataIterator) {
    this.layerMetadataIterator = layerMetadataIterator;
}

LayerMetadata.prototype.getMetadata = function (rendererCache, params, mapConfig, callback) {
    var self = this;
    var metadata = [];

    var metaQueue = queue(mapConfig.getLayers().length);

    mapConfig.getLayers().forEach(function(layer, layerId) {
        var layerType = mapConfig.layerType(layerId);

        for (var i = 0; i < self.layerMetadataIterator.length; i++) {
            if (self.layerMetadataIterator[i].is(layerType)) {
                var getMetadata = self.layerMetadataIterator[i].getMetadata.bind(self.layerMetadataIterator[i]);
                metaQueue.defer(getMetadata, mapConfig, layer, layerId, params, rendererCache);
                break;
            }
        }
    });

    metaQueue.awaitAll(function (err, results) {
        if (err) {
            return callback(err);
        }

        if (!results) {
            return callback(null, null);
        }

        mapConfig.getLayers().forEach(function(layer, layerId) {
            var layerType = mapConfig.layerType(layerId);

            metadata[layerId] = {
                type: layerType,
                meta: results[layerId]
            };

            var widgets = mapConfig.getWidgets(layerId);

            if (_.size(widgets)) {
                metadata[layerId].widgets = widgets;
            }

            var cartocss = mapConfig.getCartoCSS(layerId);

            if (cartocss) {
                metadata[layerId].meta.cartocss = cartocss;
            }
        });

        return callback(err, metadata);
    });

};

module.exports = LayerMetadata;
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const config = require('../../common/config');
const logger = require('../../common/logger');
const errors = require('../../common/errors');
const bosh = require('../../data-access-layer/bosh');
const backupStore = require('../../data-access-layer/iaas').backupStore;
const utils = require('../../common/utils');
const eventmesh = require('../../data-access-layer/eventmesh');
const Agent = require('../../data-access-layer/service-agent');
const ScheduleManager = require('../../jobs');
const CONST = require('../../common/constants');
const BaseDirectorService = require('../BaseDirectorService');
const Forbidden = errors.Forbidden;

class BackupService extends BaseDirectorService {
  constructor(plan) {
    super(plan);
    this.plan = plan;
    this.director = bosh.director;
    this.backupStore = backupStore;
    this.agent = new Agent(this.settings.agent);
  }

  getTenantGuid(context) {
    if (context.platform === CONST.PLATFORM.CF) {
      return context.space_guid;
    } else if (context.platform === CONST.PLATFORM.K8S) {
      return context.namespace;
    }
  }

  getDeploymentIps(deploymentName) {
    return this.director.getDeploymentIps(deploymentName);
  }
  //TODO-PR - static method to non-static
  static registerBnRStatusPoller(opts, instanceInfo) {
    let deploymentName = _.get(instanceInfo, 'deployment');
    const checkStatusInEveryThisMinute = config.backup.backup_restore_status_check_every / 60000;
    logger.debug(`Scheduling deployment ${deploymentName} ${opts.operation} for backup guid ${instanceInfo.backup_guid}
          ${CONST.JOB.BNR_STATUS_POLLER} for every ${checkStatusInEveryThisMinute}`);
    const repeatInterval = `*/${checkStatusInEveryThisMinute} * * * *`;
    const data = {
      operation: opts.operation,
      type: opts.type,
      trigger: opts.trigger,
      operation_details: instanceInfo
    };
    return ScheduleManager
      .schedule(
        `${deploymentName}_${opts.operation}_${instanceInfo.backup_guid}`,
        CONST.JOB.BNR_STATUS_POLLER,
        repeatInterval,
        data, {
          name: config.cf.username
        }
      );
  }

  startBackup(opts) {
    const args = opts.arguments;
    const backup = _
      .chain(opts)
      .pick('guid')
      .assign({
        type: _.get(args, 'type', 'online'),
        secret: undefined,
        trigger: _.get(args, 'trigger', CONST.BACKUP.TRIGGER.ON_DEMAND)
      })
      .value();
    const backupStartedAt = new Date().toISOString();
    const data = _
      .chain(opts)
      .pick('service_id', 'plan_id', 'organization_guid', 'instance_guid', 'username')
      .assign({
        operation: CONST.OPERATION_TYPE.BACKUP,
        type: backup.type,
        backup_guid: backup.guid,
        trigger: backup.trigger,
        state: 'processing',
        secret: undefined,
        agent_ip: undefined,
        started_at: backupStartedAt,
        finished_at: null,
        tenant_id: opts.context ? this.getTenantGuid(opts.context) : args.space_guid
      })
      .value();

    let instanceInfo;
    const result = _
      .chain(opts)
      .pick('deployment')
      .assign({
        subtype: CONST.OPERATION_TYPE.BACKUP,
        backup_guid: backup.guid,
        agent_ip: undefined,
        tenant_id: opts.context ? this.getTenantGuid(opts.context) : args.space_guid,
        description: `${backup.trigger} backup triggerred by ${data.username} at ${data.started_at}`
      })
      .value();

    function createSecret() {
      return utils
        .randomBytes(12)
        .then(buffer => buffer.toString('base64'));
    }

    function normalizeVm(vm) {
      let vmParams = _.pick(vm, 'cid', 'agent_id', 'job', 'index');
      return _.set(vmParams, 'iaas_vm_metadata.vm_id', config.backup.provider.name === CONST.IAAS.AZURE ? vmParams.agent_id : vmParams.cid);
    }

    let metaUpdated = false,
      backupStarted = false,
      registeredStatusPoller = false;

    let deploymentName;
    return bosh
      .director
      .getDeploymentNameForInstanceId(opts.instance_guid).then(res => {
        deploymentName = res;
        logger.info('Obtained the deployment name for instance :', deploymentName);
      })
      .then(() => Promise.all([
        createSecret(),
        this.getDeploymentIps(deploymentName),
        this.director.getDeploymentVms(deploymentName).map(normalizeVm)
      ]))
      .spread((secret, ips, vms) => {
        // set data and backup secret
        logger.info(`Starting backup on - ${deploymentName}. Agent Ips for deployment - `, ips);
        data.secret = backup.secret = secret;
        return this.agent
          .getHost(ips, 'backup')
          .then(agent_ip => {
            data.agent_ip = result.agent_ip = agent_ip;
            instanceInfo = _.chain(data)
              .pick('tenant_id', 'backup_guid', 'instance_guid', 'agent_ip', 'service_id', 'plan_id')
              .set('deployment', deploymentName)
              .set('started_at', backupStartedAt)
              .value();
            return BackupService.registerBnRStatusPoller({
                operation: CONST.OPERATION_TYPE.BACKUP,
                type: backup.type,
                trigger: backup.trigger
              }, instanceInfo)
              .return(agent_ip);
          })
          .then(agent_ip => {
            registeredStatusPoller = true;
            return this.agent.startBackup(agent_ip, backup, vms);
          })
          .then(() => {
            backupStarted = true;
            let put_ret = this.backupStore.putFile(data);
            logger.debug(put_ret);
            return data;
          });
      })
      //TODO-PR - Break it into multiple methods
      .then(backupInfo => {
        return eventmesh.apiServerClient.updateOperationStateAndResponse({
            resourceId: opts.instance_guid,
            operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
            operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
            operationId: result.backup_guid,
            stateValue: CONST.APISERVER.RESOURCE_STATE.IN_PROGRESS,
            response: backupInfo
          })
          .then(() => backupInfo);
      })
      .catch(err => {
        return Promise
          .try(() => logger.error(`Error during start of backup - backup to be aborted : ${backupStarted} - backup to be deleted: ${metaUpdated} `, err))
          .tap(() => {
            if (registeredStatusPoller) {
              logger.error(`Error occurred during backup process. Cancelling status poller for deployment : ${deploymentName} and backup_guid: ${backup.guid}`);
              return ScheduleManager
                .cancelSchedule(`${deploymentName}_backup_${backup.guid}`,
                  CONST.JOB.BNR_STATUS_POLLER)
                .catch((err) => logger.error('Error occurred while performing clean up of backup failure operation : ', err));
            }
          })
          .then(() => eventmesh.apiServerClient.updateOperationStatus({
            resourceId: opts.instance_guid,
            operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
            operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
            operationId: result.backup_guid,
            stateValue: CONST.APISERVER.RESOURCE_STATE.FAILED,
            error: err
          }))
          .then(() => {
            if (backupStarted) {
              logger.error(`Error occurred during backup process. Aborting backup on deployment : ${deploymentName}`);
              return this
                .abortLastBackup(opts, true)
                .finally(() => {
                  if (metaUpdated) {
                    const options = _
                      .chain(data)
                      .pick(data, 'tenant_id', 'backup_guid')
                      .set('force', true)
                      .value();
                    logger.error(`Error occurred during backup process. Deleting backup file on deployment : ${deploymentName} - backup file:`, options);
                    return this.backupStore
                      .deleteBackupFile(options);
                  }
                })
                .catch((err) => logger.error('Error occurred while performing clean up of backup failure operation : ', err));
            }
          }).then(() => {
            throw err;
          });
      });
  }

  getOperationState(name, opts) {
    logger.info(`Retrieving state of last Backup with:`, opts);
    return this.getBackupOperationState(opts)
      .then(result => {
        const deploymentName = opts.deployment;
        const action = _.capitalize(name);
        const timestamp = result.updated_at;
        //TODO-PR - Try to move to BaseService
        switch (result.state) {
        case CONST.BACKUP_OPERATION.SUCCEEDED:
          return {
            description: `${action} deployment ${deploymentName} succeeded at ${timestamp}`,
            state: CONST.BACKUP_OPERATION.SUCCEEDED
          };
        case CONST.BACKUP_OPERATION.ABORTED:
          return {
            description: `${action} deployment ${deploymentName} aborted at ${timestamp}`,
            state: CONST.BACKUP_OPERATION.FAILED
          };
        case CONST.BACKUP_OPERATION.FAILED:
          return {
            description: `${action} deployment ${deploymentName} failed at ${timestamp} with Error "${result.stage}"`,
            state: CONST.BACKUP_OPERATION.FAILED
          };
        default:
          return {
            description: `${action} deployment ${deploymentName} is still in progress: "${result.stage}"`,
            state: CONST.BACKUP_OPERATION.PROCESSING
          };
        }
      });
  }

  getLastBackup(tenant_id, instance_guid) {
    return this.backupStore
      .getBackupFile({
        tenant_id: tenant_id,
        service_id: this.plan.service.id,
        plan_id: this.plan.id,
        instance_guid: instance_guid
      });
  }

  getBackupOperationState(opts) {
    logger.debug('Getting Backup operation State for:', opts);
    const agent_ip = opts.agent_ip;
    const options = _.assign({
      service_id: this.plan.service.id,
      plan_id: this.plan.id,
      tenant_id: opts.context ? this.getTenantGuid(opts.context) : opts.tenant_id
    }, opts);

    function isFinished(state) {
      return _.includes(['succeeded', 'failed', 'aborted'], state);
    }

    return this.agent
      .getBackupLastOperation(agent_ip)
      .tap(lastOperation => {
        if (isFinished(lastOperation.state)) {
          return this.agent
            .getBackupLogs(agent_ip)
            .tap(logs => _.each(logs, log => logger.info(`Backup log for: ${opts.instance_guid} - ${JSON.stringify(log)}`)))
            .then(logs => this.backupStore
              .patchBackupFile(options, {
                state: lastOperation.state,
                logs: logs,
                snapshotId: lastOperation.snapshotId
              })
            )
            .then(() => this.backupStore.getBackupFile(options))
            .then(metadata => eventmesh.apiServerClient.updateOperationResponse({
              resourceId: options.instance_guid,
              operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
              operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
              operationId: metadata.backup_guid,
              value: metadata
            }));
        }
      });
  }

  /**
   * @description Delete backup from backup store and Apiserver
   * @param {string} options.tenant_id
   * @param {string} options.service_id
   * @param {string} options.instance_guid
   * @param {string} options.backup_guid
   * @param {string} options.time_stamp
   */
  deleteBackup(options) {
    logger.info('Attempting delete with:', options);
    return this.backupStore
      .deleteBackupFile(options)
      .then(() => eventmesh.apiServerClient.updateOperationState({
        resourceId: options.instance_guid,
        operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
        operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
        operationId: options.backup_guid,
        stateValue: CONST.APISERVER.RESOURCE_STATE.DELETED
      }))
      .catch(err => {
        return Promise
          .try(() => logger.error(`Error during delete of backup`, err))
          .then(() => eventmesh.apiServerClient.updateOperationStatus({
            resourceId: options.instance_guid,
            operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
            operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
            operationId: options.backup_guid,
            stateValue: CONST.APISERVER.RESOURCE_STATE.DELETE_FAILED,
            error: err
          }));
      });
  }

  abortLastBackup(abortOptions, force) {
    logger.info('Starting abort with following options:', abortOptions);
    return this.backupStore
      .getBackupFile({
        tenant_id: this.getTenantGuid(abortOptions.context),
        service_id: abortOptions.service_id,
        instance_guid: abortOptions.instance_guid
      }).then(metadata => {
        if (!force && metadata.trigger === CONST.BACKUP.TRIGGER.SCHEDULED) {
          throw new Forbidden('System scheduled backup runs cannot be aborted');
        }
        switch (metadata.state) {
        case 'processing':
          return this.agent
            .abortBackup(metadata.agent_ip)
            .then(() => eventmesh.apiServerClient.updateOperationState({
              resourceId: abortOptions.instance_guid,
              operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
              operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
              operationId: abortOptions.guid,
              stateValue: CONST.OPERATION.ABORTING
            }))
            .return({
              state: CONST.OPERATION.ABORTING
            });
        default:
          return _.pick(metadata, 'state');
        }
      });
  }

  static createService(plan) {
    if (!this[plan.id]) {
      this[plan.id] = new this(plan);
    }
    return Promise.resolve(this[plan.id]);
  }

}

module.exports = BackupService;<commit_msg>Avoid Call to backupStore<commit_after>'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const config = require('../../common/config');
const logger = require('../../common/logger');
const errors = require('../../common/errors');
const bosh = require('../../data-access-layer/bosh');
const backupStore = require('../../data-access-layer/iaas').backupStore;
const utils = require('../../common/utils');
const eventmesh = require('../../data-access-layer/eventmesh');
const Agent = require('../../data-access-layer/service-agent');
const ScheduleManager = require('../../jobs');
const CONST = require('../../common/constants');
const BaseDirectorService = require('../BaseDirectorService');
const Forbidden = errors.Forbidden;

class BackupService extends BaseDirectorService {
  constructor(plan) {
    super(plan);
    this.plan = plan;
    this.director = bosh.director;
    this.backupStore = backupStore;
    this.agent = new Agent(this.settings.agent);
  }

  getTenantGuid(context) {
    if (context.platform === CONST.PLATFORM.CF) {
      return context.space_guid;
    } else if (context.platform === CONST.PLATFORM.K8S) {
      return context.namespace;
    }
  }

  getDeploymentIps(deploymentName) {
    return this.director.getDeploymentIps(deploymentName);
  }
  //TODO-PR - static method to non-static
  static registerBnRStatusPoller(opts, instanceInfo) {
    let deploymentName = _.get(instanceInfo, 'deployment');
    const checkStatusInEveryThisMinute = config.backup.backup_restore_status_check_every / 60000;
    logger.debug(`Scheduling deployment ${deploymentName} ${opts.operation} for backup guid ${instanceInfo.backup_guid}
          ${CONST.JOB.BNR_STATUS_POLLER} for every ${checkStatusInEveryThisMinute}`);
    const repeatInterval = `*/${checkStatusInEveryThisMinute} * * * *`;
    const data = {
      operation: opts.operation,
      type: opts.type,
      trigger: opts.trigger,
      operation_details: instanceInfo
    };
    return ScheduleManager
      .schedule(
        `${deploymentName}_${opts.operation}_${instanceInfo.backup_guid}`,
        CONST.JOB.BNR_STATUS_POLLER,
        repeatInterval,
        data, {
          name: config.cf.username
        }
      );
  }

  startBackup(opts) {
    const args = opts.arguments;
    const backup = _
      .chain(opts)
      .pick('guid')
      .assign({
        type: _.get(args, 'type', 'online'),
        secret: undefined,
        trigger: _.get(args, 'trigger', CONST.BACKUP.TRIGGER.ON_DEMAND)
      })
      .value();
    const backupStartedAt = new Date().toISOString();
    const data = _
      .chain(opts)
      .pick('service_id', 'plan_id', 'organization_guid', 'instance_guid', 'username')
      .assign({
        operation: CONST.OPERATION_TYPE.BACKUP,
        type: backup.type,
        backup_guid: backup.guid,
        trigger: backup.trigger,
        state: 'processing',
        secret: undefined,
        agent_ip: undefined,
        started_at: backupStartedAt,
        finished_at: null,
        tenant_id: opts.context ? this.getTenantGuid(opts.context) : args.space_guid
      })
      .value();

    let instanceInfo;
    const result = _
      .chain(opts)
      .pick('deployment')
      .assign({
        subtype: CONST.OPERATION_TYPE.BACKUP,
        backup_guid: backup.guid,
        agent_ip: undefined,
        tenant_id: opts.context ? this.getTenantGuid(opts.context) : args.space_guid,
        description: `${backup.trigger} backup triggerred by ${data.username} at ${data.started_at}`
      })
      .value();

    function createSecret() {
      return utils
        .randomBytes(12)
        .then(buffer => buffer.toString('base64'));
    }

    function normalizeVm(vm) {
      let vmParams = _.pick(vm, 'cid', 'agent_id', 'job', 'index');
      return _.set(vmParams, 'iaas_vm_metadata.vm_id', config.backup.provider.name === CONST.IAAS.AZURE ? vmParams.agent_id : vmParams.cid);
    }

    let metaUpdated = false,
      backupStarted = false,
      registeredStatusPoller = false;

    let deploymentName;
    return bosh
      .director
      .getDeploymentNameForInstanceId(opts.instance_guid).then(res => {
        deploymentName = res;
        logger.info('Obtained the deployment name for instance :', deploymentName);
      })
      .then(() => Promise.all([
        createSecret(),
        this.getDeploymentIps(deploymentName),
        this.director.getDeploymentVms(deploymentName).map(normalizeVm)
      ]))
      .spread((secret, ips, vms) => {
        // set data and backup secret
        logger.info(`Starting backup on - ${deploymentName}. Agent Ips for deployment - `, ips);
        data.secret = backup.secret = secret;
        return this.agent
          .getHost(ips, 'backup')
          .then(agent_ip => {
            data.agent_ip = result.agent_ip = agent_ip;
            instanceInfo = _.chain(data)
              .pick('tenant_id', 'backup_guid', 'instance_guid', 'agent_ip', 'service_id', 'plan_id')
              .set('deployment', deploymentName)
              .set('started_at', backupStartedAt)
              .value();
            return BackupService.registerBnRStatusPoller({
                operation: CONST.OPERATION_TYPE.BACKUP,
                type: backup.type,
                trigger: backup.trigger
              }, instanceInfo)
              .return(agent_ip);
          })
          .then(agent_ip => {
            registeredStatusPoller = true;
            return this.agent.startBackup(agent_ip, backup, vms);
          })
          .then(() => {
            backupStarted = true;
            let put_ret = this.backupStore.putFile(data);
            logger.debug(put_ret);
            return data;
          });
      })
      //TODO-PR - Break it into multiple methods
      .then(backupInfo => {
        return eventmesh.apiServerClient.updateOperationStateAndResponse({
            resourceId: opts.instance_guid,
            operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
            operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
            operationId: result.backup_guid,
            stateValue: CONST.APISERVER.RESOURCE_STATE.IN_PROGRESS,
            response: backupInfo
          })
          .then(() => backupInfo);
      })
      .catch(err => {
        return Promise
          .try(() => logger.error(`Error during start of backup - backup to be aborted : ${backupStarted} - backup to be deleted: ${metaUpdated} `, err))
          .tap(() => {
            if (registeredStatusPoller) {
              logger.error(`Error occurred during backup process. Cancelling status poller for deployment : ${deploymentName} and backup_guid: ${backup.guid}`);
              return ScheduleManager
                .cancelSchedule(`${deploymentName}_backup_${backup.guid}`,
                  CONST.JOB.BNR_STATUS_POLLER)
                .catch((err) => logger.error('Error occurred while performing clean up of backup failure operation : ', err));
            }
          })
          .then(() => eventmesh.apiServerClient.updateOperationStatus({
            resourceId: opts.instance_guid,
            operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
            operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
            operationId: result.backup_guid,
            stateValue: CONST.APISERVER.RESOURCE_STATE.FAILED,
            error: err
          }))
          .then(() => {
            if (backupStarted) {
              logger.error(`Error occurred during backup process. Aborting backup on deployment : ${deploymentName}`);
              return this
                .abortLastBackup(opts, true)
                .finally(() => {
                  if (metaUpdated) {
                    const options = _
                      .chain(data)
                      .pick(data, 'tenant_id', 'backup_guid')
                      .set('force', true)
                      .value();
                    logger.error(`Error occurred during backup process. Deleting backup file on deployment : ${deploymentName} - backup file:`, options);
                    return this.backupStore
                      .deleteBackupFile(options);
                  }
                })
                .catch((err) => logger.error('Error occurred while performing clean up of backup failure operation : ', err));
            }
          }).then(() => {
            throw err;
          });
      });
  }

  getOperationState(name, opts) {
    logger.info(`Retrieving state of last Backup with:`, opts);
    return this.getBackupOperationState(opts)
      .then(result => {
        const deploymentName = opts.deployment;
        const action = _.capitalize(name);
        const timestamp = result.updated_at;
        //TODO-PR - Try to move to BaseService
        switch (result.state) {
        case CONST.BACKUP_OPERATION.SUCCEEDED:
          return {
            description: `${action} deployment ${deploymentName} succeeded at ${timestamp}`,
            state: CONST.BACKUP_OPERATION.SUCCEEDED
          };
        case CONST.BACKUP_OPERATION.ABORTED:
          return {
            description: `${action} deployment ${deploymentName} aborted at ${timestamp}`,
            state: CONST.BACKUP_OPERATION.FAILED
          };
        case CONST.BACKUP_OPERATION.FAILED:
          return {
            description: `${action} deployment ${deploymentName} failed at ${timestamp} with Error "${result.stage}"`,
            state: CONST.BACKUP_OPERATION.FAILED
          };
        default:
          return {
            description: `${action} deployment ${deploymentName} is still in progress: "${result.stage}"`,
            state: CONST.BACKUP_OPERATION.PROCESSING
          };
        }
      });
  }

  getLastBackup(tenant_id, instance_guid) {
    return this.backupStore
      .getBackupFile({
        tenant_id: tenant_id,
        service_id: this.plan.service.id,
        plan_id: this.plan.id,
        instance_guid: instance_guid
      });
  }

  getBackupOperationState(opts) {
    logger.debug('Getting Backup operation State for:', opts);
    const agent_ip = opts.agent_ip;
    const options = _.assign({
      service_id: this.plan.service.id,
      plan_id: this.plan.id,
      tenant_id: opts.context ? this.getTenantGuid(opts.context) : opts.tenant_id
    }, opts);

    function isFinished(state) {
      return _.includes(['succeeded', 'failed', 'aborted'], state);
    }

    return this.agent
      .getBackupLastOperation(agent_ip)
      .tap(lastOperation => {
        if (isFinished(lastOperation.state)) {
          return this.agent
            .getBackupLogs(agent_ip)
            .tap(logs => _.each(logs, log => logger.info(`Backup log for: ${opts.instance_guid} - ${JSON.stringify(log)}`)))
            .then(logs => this.backupStore
              .patchBackupFile(options, {
                state: lastOperation.state,
                logs: logs,
                snapshotId: lastOperation.snapshotId
              })
              .return(logs);
            )
            .then(metadata => eventmesh.apiServerClient.patchOperationResponse({
              resourceId: options.instance_guid,
              operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
              operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
              operationId: metadata.backup_guid,
              value: metadata
            }));
        }
      });
  }

  /**
   * @description Delete backup from backup store and Apiserver
   * @param {string} options.tenant_id
   * @param {string} options.service_id
   * @param {string} options.instance_guid
   * @param {string} options.backup_guid
   * @param {string} options.time_stamp
   */
  deleteBackup(options) {
    logger.info('Attempting delete with:', options);
    return this.backupStore
      .deleteBackupFile(options)
      .then(() => eventmesh.apiServerClient.updateOperationState({
        resourceId: options.instance_guid,
        operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
        operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
        operationId: options.backup_guid,
        stateValue: CONST.APISERVER.RESOURCE_STATE.DELETED
      }))
      .catch(err => {
        return Promise
          .try(() => logger.error(`Error during delete of backup`, err))
          .then(() => eventmesh.apiServerClient.updateOperationStatus({
            resourceId: options.instance_guid,
            operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
            operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
            operationId: options.backup_guid,
            stateValue: CONST.APISERVER.RESOURCE_STATE.DELETE_FAILED,
            error: err
          }));
      });
  }

  abortLastBackup(abortOptions, force) {
    logger.info('Starting abort with following options:', abortOptions);
    return this.backupStore
      .getBackupFile({
        tenant_id: this.getTenantGuid(abortOptions.context),
        service_id: abortOptions.service_id,
        instance_guid: abortOptions.instance_guid
      }).then(metadata => {
        if (!force && metadata.trigger === CONST.BACKUP.TRIGGER.SCHEDULED) {
          throw new Forbidden('System scheduled backup runs cannot be aborted');
        }
        switch (metadata.state) {
        case 'processing':
          return this.agent
            .abortBackup(metadata.agent_ip)
            .then(() => eventmesh.apiServerClient.updateOperationState({
              resourceId: abortOptions.instance_guid,
              operationName: CONST.APISERVER.ANNOTATION_NAMES.BACKUP,
              operationType: CONST.APISERVER.ANNOTATION_TYPES.BACKUP,
              operationId: abortOptions.guid,
              stateValue: CONST.OPERATION.ABORTING
            }))
            .return({
              state: CONST.OPERATION.ABORTING
            });
        default:
          return _.pick(metadata, 'state');
        }
      });
  }

  static createService(plan) {
    if (!this[plan.id]) {
      this[plan.id] = new this(plan);
    }
    return Promise.resolve(this[plan.id]);
  }

}

module.exports = BackupService;<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
var api = {};
(function(api) {
    var log = new Log('rating-api');
    api.rate = function(session, options) {
        var success = false; //Assume the rating will fail
        if (!options.type) {
            log.error('Unable to rate assets without a type');
            return success;
        }
        if (!options.id) {
            log.error('Unable to rate assets without an id.');
            return success;
        }
        if (!options.value) {
            log.error('Cannot rate the asset when there is no rating value provided.');
            return success;
        }
        var am = getAssetManager(session, options.type);
        try {
            success = am.rate(options.id, options.value);
        } catch (e) {
            log.error('Could not rate the asset type: ' + options.type + ' id: ' + options.id + ' with rating: ' + options.value + '.Exception: ' + e);
        }
        return success;
    };
    /**
     * The function adds rating details to the provided assets
     * @param {[type]} assets   [description]
     * @param {[type]} tenantId [description]
     * @param {[type]} username [description]
     */
    api.addRatings = function(assets,am,tenantId, username) {
        var utils=require('utils').reflection;
        var tenantId = tenantId;
        var id;
        var rating;
        var average;
        //Determine if a single asset has been provided
        if(!utils.isArray(assets)){
            assets=[assets];
        }
        for (var index in assets) {
            id = assets[index].id;
            rating = am.rating(id, username);
            average = rating ? rating.average : 0;
            assets[index].rating = rating ? rating.user : 0;
            assets[index].avgRating = average;
            assets[index].ratingPx = calculateRatingPixel(average);
        }
    };
    var calculateRatingPixel = function(avgRating) {
        var STAR_WIDTH = 84;
        var MAX_RATING = 5;
        var ratingPx = (avgRating / MAX_RATING) * STAR_WIDTH;
        return ratingPx;
    };
    var getAssetManager = function(session, type) {
        var asset = require('rxt').asset;
        var am = asset.createUserAssetManager(session, type);
        return am;
    }
}(api));<commit_msg>fixed showing unrelated rating in store<commit_after>/*
 *  Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
var api = {};
(function(api) {
    var log = new Log('rating-api');
    api.rate = function(session, options) {
        var success = false; //Assume the rating will fail
        if (!options.type) {
            log.error('Unable to rate assets without a type');
            return success;
        }
        if (!options.id) {
            log.error('Unable to rate assets without an id.');
            return success;
        }
        if (!options.value) {
            log.error('Cannot rate the asset when there is no rating value provided.');
            return success;
        }
        var am = getAssetManager(session, options.type);
        try {
            var asst = am.get(options.id);
            success = am.rate(asst.path, options.value);
            //TODO get a api from registry to get ratings by id
        } catch (e) {
            log.error('Could not rate the asset type: ' + options.type + ' id: ' + options.id + ' with rating: ' + options.value + '.Exception: ' + e);
        }
        return success;
    };
    /**
     * The function adds rating details to the provided assets
     * @param {[type]} assets   [description]
     * @param {[type]} tenantId [description]
     * @param {[type]} username [description]
     */
    api.addRatings = function(assets,am,tenantId, username) {
        var utils=require('utils').reflection;
        var tenantId = tenantId;
        var id;
        var rating;
        var average;
        //Determine if a single asset has been provided
        if(!utils.isArray(assets)){
            assets=[assets];
        }
        for (var index in assets) {
            rating = am.rating(assets[index].path, username);

            average = rating ? rating.average : 0;
            assets[index].rating = rating ? rating.user : 0;
            assets[index].avgRating = average;
            assets[index].ratingPx = calculateRatingPixel(average);
        }
    };
    var calculateRatingPixel = function(avgRating) {
        var STAR_WIDTH = 84;
        var MAX_RATING = 5;
        var ratingPx = (avgRating / MAX_RATING) * STAR_WIDTH;
        return ratingPx;
    };
    var getAssetManager = function(session, type) {
        var asset = require('rxt').asset;
        var am = asset.createUserAssetManager(session, type);
        return am;
    }
}(api));<|endoftext|>


<commit_before>Module.register("command_center", {

  // Module config defaults.
  defaults: {
  },

  // Override dom generator.
  getDom: function() {

    var wrapper = document.createElement("div");

    // Return the wrapper to the dom.
    return wrapper;
  },

  start: function() {
    this.sendSocketNotification("START_SERVER");
  },

  notificationReceived: function(notification, payload, sender) {
    return;
  },

  socketNotificationReceived: function(notification, payload) {
    console.log("command_center got: " + notification);
    this.sendNotification(notification);
  }

});
<commit_msg>command center<commit_after>Module.register("command_center", {

  // Module config defaults.
  defaults: {
  },

  // Override dom generator.
  getDom: function() {

    var wrapper = document.createElement("div");

    // Return the wrapper to the dom.
    return wrapper;
  },

  start: function() {
    this.sendSocketNotification("START_SERVER");
  },

  notificationReceived: function(notification, payload, sender) {
    return;
  },

  socketNotificationReceived: function(notification, payload){
    if (notification === "COMMAND_RECIEVED"){
        //Broadcast the message
        console.error(notification);
        this.sendNotification(notification);
    }
  }
});
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Sebastian
// Date:      04.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('model.js');

/**
 * @class
 *
 * The root object for ModelManager.
 *
 * A ModelManager is used by a controller and is an interface that makes it easy for him to
 * handle his model records.
 * Even if each controller has his own Model Manager, the id for each model is not bound to this one model manager,
 * but is synchronized with the model registry to solve conflicts when saving models to storage.
 */
M.ModelManager = M.Object.extend({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.ModelManager',

    /**
     * Array containing all models of this model manager.
     *
     * @property {Object}
     */
    modelList: [],

    /**
     * Add the given model to the modelList but first.
     *
     * @param model
     */
    add: function(model) {
        this.modelList.push(model);
    },

    removeAll: function() {
        this.modelList = [];
    },

    remove: function(id) {
        if(typeof(id) === 'string') {
            id = parseInt(id);
        }
        model = this.getModelForId(id);
        if(model) {
            this.modelList = _.select(this.modelList, function(m){
                return m.id !== model.id;
            });
        }
    },

    getModelForId: function(id) {
        var model = _.detect(this.modelList, function(m){
            return m.id === id;
        });
        return model;
    },

    /**
     * The following methods encapsulate operations to the storage system.
     *
     */

    /**
     * Saves (persists) all records in modelList to the storage system.
     */
    saveAll: function() {
        _.each(this.modelList, function(m){
            m.save();
        });
    },

    /**
     * Save the model record, identified by id, to the storage system.
     *
     * @param {Number} id Identifies the model record.
     */
    save: function(id) {
        var model = this.getModelForId(id);
        if(model) {
            model.save();
        }
    },

    /**
     * Deletes all records in modelList from the storage system.
     */
    delAll: function() {
        _.each(this.modelList, function(m){
            m.del();
        });
    },

    /**
     * Delete the model record, identified by id, from the storage system.
     *
     * @param {Number} id Identifies the model record.
     */
    del: function(id) {
        var model = this.getModelForId(id);
        if(model) {
            model.del();
        }
    },

    /**
     * Find stored model records.
     */
    find: function() {
        var modelName = this.model.name;
        for(var i = 0; i < localStorage.length; i++) {
            var key = localStorage.key(i);
            /* Search for keys like: Model_123 (regex: ^Model_\d+) */
            var regexResult = new RegExp('^' + this.model.name + '_(\\d+)').exec(key);
            if(regexResult && regexResult[1]) {
                var obj = JSON.parse(localStorage.getItem(key));
                obj.id = parseInt(regexResult[1]);
                var model = this.model.newRecord(obj);
                this.add(model);
            }
        };
        this.modelList = _.sortBy(this.modelList, function(m) {
            return m.id;
        });
    },

    /**
     * Debug method to print out all content from the modelList array to the console.
     */
    dumpModelList: function() {
        _.each(this.modelList, function(model){
            console.log(model.id);
        });
    }

});<commit_msg>typos and comments<commit_after>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Sebastian
// Date:      04.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('model.js');

/**
 * @class
 *
 * The root object for ModelManager.
 *
 * A ModelManager is used by a controllers and is an interface that makes it easy for him to
 * handle his model records.
 * Even if each controllers has his own Model Manager, the id for each model is not bound to this one model manager,
 * but is synchronized with the model registry to solve conflicts when saving models to storage.
 */
M.ModelManager = M.Object.extend({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.ModelManager',

    /**
     * Array containing all models of this model manager.
     *
     * @property {Object}
     */
    modelList: [],

    /**
     * Add the given model to the modelList.
     *
     * @param model
     */
    add: function(model) {
        this.modelList.push(model);
    },

    removeAll: function() {
        this.modelList = [];
    },

    remove: function(id) {
        if(typeof(id) === 'string') {
            id = parseInt(id);
        }
        model = this.getModelForId(id);
        if(model) {
            this.modelList = _.select(this.modelList, function(m){
                return m.id !== model.id;
            });
        }
    },

    getModelForId: function(id) {
        var model = _.detect(this.modelList, function(m){
            return m.id === id;
        });
        return model;
    },

    /**
     * The following methods encapsulate operations to the storage system.
     *
     */

    /**
     * Saves (persists) all records in modelList to the storage system.
     */
    saveAll: function() {
        _.each(this.modelList, function(m){
            m.save();
        });
    },

    /**
     * Save the model record, identified by id, to the storage system.
     *
     * @param {Number} id Identifies the model record.
     */
    save: function(id) {
        var model = this.getModelForId(id);
        if(model) {
            model.save();
        }
    },

    /**
     * Deletes all records in modelList from the storage system.
     */
    delAll: function() {
        _.each(this.modelList, function(m){
            m.del();
        });
    },

    /**
     * Delete the model record, identified by id, from the storage system.
     *
     * @param {Number} id Identifies the model record.
     */
    del: function(id) {
        var model = this.getModelForId(id);
        if(model) {
            model.del();
        }
    },

    /**
     * Find stored model records.
     */
    find: function() {
        var modelName = this.model.name;
        for(var i = 0; i < localStorage.length; i++) {
            var key = localStorage.key(i);
            /* Search for keys like: Model_123 (regex: ^Model_\d+) */
            var regexResult = new RegExp('^' + this.model.name + '_(\\d+)').exec(key);
            if(regexResult && regexResult[1]) {
                var obj = JSON.parse(localStorage.getItem(key));
                obj.id = parseInt(regexResult[1]);
                var model = this.model.newRecord(obj);
                this.add(model);
            }
        };
        this.modelList = _.sortBy(this.modelList, function(m) {
            return m.id;
        });
    },

    /**
     * Debug method to print out all content from the modelList array to the console.
     */
    dumpModelList: function() {
        _.each(this.modelList, function(model){
            console.log(model.id);
        });
    }

});<|endoftext|>