<commit_before>import TraceGatherer from '../../src/gatherer/trace'
import { FRAME_LOAD_START_TIMEOUT } from '../../src/constants'

import TRACELOG from '../__fixtures__/tracelog.json'

let traceGatherer

const pageMock = {
    on: jest.fn(),
    tracing: {
        start: jest.fn(),
        stop: jest.fn().mockReturnValue(Promise.resolve(Buffer.from(JSON.stringify(TRACELOG))))
    },
    evaluateOnNewDocument: jest.fn()
}
const sessionMock = {
    on: jest.fn(),
    removeListener: jest.fn()
}
const driverMock = {
    getCDPSession: jest.fn().mockReturnValue(Promise.resolve(sessionMock)),
    getActivePage: jest.fn().mockReturnValue(Promise.resolve(pageMock))
}

const frame = {
    id: 123,
    loaderId: 'foobar123',
    url: 'http://foobar.com'
}

beforeEach(() => {
    driverMock.getCDPSession.mockClear()
    driverMock.getActivePage.mockClear()
    pageMock.on.mockClear()
    pageMock.tracing.start.mockClear()
    pageMock.tracing.stop.mockClear()
    pageMock.evaluateOnNewDocument.mockClear()
    sessionMock.on.mockClear()
    traceGatherer = new TraceGatherer(driverMock)
    traceGatherer.emit = jest.fn()
})

test('should register eventlisteners for network monitor', () => {
    traceGatherer.networkStatusMonitor = { dispatch: jest.fn() }
    for (const fn of Object.values(traceGatherer.networkListeners)) {
        fn({ some: 'params' })
    }
    expect(traceGatherer.networkStatusMonitor.dispatch.mock.calls)
        .toMatchSnapshot()
})

test('onFrameLoadFail', () => {
    traceGatherer.onFrameLoadFail({ frame: () => ({ _id: 123 }) })
    expect(traceGatherer.failingFrameLoadIds).toEqual([123])
})

test('getter isTracing', () => {
    expect(traceGatherer.isTracing).toBe(false)

    traceGatherer.traceStart = Date.now()
    expect(traceGatherer.isTracing).toBe(true)
})

test('finishTracing', async () => {
    traceGatherer.pageLoadDetected = true
    traceGatherer.networkStatusMonitor = true
    traceGatherer.traceStart = true
    traceGatherer.frameId = true
    traceGatherer.loaderId = true
    traceGatherer.pageUrl = true
    traceGatherer.waitForNetworkIdleEvent = { cancel: jest.fn() }
    traceGatherer.waitForCPUIdleEvent = { cancel: jest.fn() }

    traceGatherer.finishTracing()
    await new Promise((resolve) => process.nextTick(resolve))

    expect(traceGatherer.pageLoadDetected).toBe(false)
    expect(traceGatherer.networkStatusMonitor).toBe(undefined)
    expect(traceGatherer.traceStart).toBe(undefined)
    expect(traceGatherer.frameId).toBe(undefined)
    expect(traceGatherer.loaderId).toBe(undefined)
    expect(traceGatherer.pageUrl).toBe(undefined)

    expect(sessionMock.removeListener).toHaveBeenCalledTimes(7)
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingFinished')
})

test('onFrameNavigated', () => {
    expect(traceGatherer.frameId).toBe(undefined)
    expect(traceGatherer.loaderId).toBe(undefined)
    expect(traceGatherer.pageUrl).toBe(undefined)
    traceGatherer.traceStart = Date.now()

    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toBeCalledWith('tracingStarted', 123)

    expect(traceGatherer.frameId).toBe(123)
    expect(traceGatherer.loaderId).toBe('foobar123')
    expect(traceGatherer.pageUrl).toBe('http://foobar.com')
})

test('onFrameNavigated: should not start if frame was detected', () => {
    traceGatherer.frameId = 123
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if a subframe was detected', () => {
    const subFrame = Object.assign({}, frame, { parentId: 122 })
    traceGatherer.onFrameNavigated({ frame: subFrame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if url is not supported', () => {
    const subFrame = Object.assign({}, frame, { url: 'data:,foobar' })
    traceGatherer.onFrameNavigated({ frame: subFrame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if tracing is not started', () => {
    traceGatherer.traceStart = undefined
    traceGatherer.finishTracing = jest.fn()
    traceGatherer.failingFrameLoadIds.push(123)
    traceGatherer.waitForNetworkIdleEvent = { cancel: jest.fn() }
    traceGatherer.waitForCPUIdleEvent = { cancel: jest.fn() }
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(0)
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(0)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should cancel trace if page load failed', () => {
    traceGatherer.traceStart = Date.now()
    traceGatherer.finishTracing = jest.fn()
    traceGatherer.failingFrameLoadIds.push(123)
    traceGatherer.waitForNetworkIdleEvent = { cancel: jest.fn() }
    traceGatherer.waitForCPUIdleEvent = { cancel: jest.fn() }
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toHaveBeenCalledWith('tracingError', expect.any(Error))
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(1)
})

test('onFrameNavigated: can detect page load', () => {
    traceGatherer.clickTraceTimeout = true
    traceGatherer.traceStart = Date.now()

    expect(traceGatherer.pageLoadDetected).toBe(false)
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.pageLoadDetected).toBe(true)
})

test('startTracing', async () => {
    traceGatherer.waitForNetworkIdle = jest.fn()
    traceGatherer.waitForCPUIdle = jest.fn()
    await traceGatherer.startTracing('http://foobar.com')

    expect(sessionMock.on).toHaveBeenCalledTimes(7)
    expect(pageMock.tracing.start).toHaveBeenCalledTimes(1)
    expect(pageMock.evaluateOnNewDocument).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForNetworkIdle).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdle).toHaveBeenCalledTimes(1)
})

test('startTracing: registers timeout for click events', async () => {
    traceGatherer.waitForNetworkIdle = jest.fn()
    traceGatherer.waitForCPUIdle = jest.fn()
    traceGatherer.finishTracing = jest.fn()

    await traceGatherer.startTracing('click event')
    await new Promise((resolve) => setTimeout(resolve, FRAME_LOAD_START_TIMEOUT + 10))
    expect(pageMock.tracing.stop).toHaveBeenCalledTimes(1)
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
})

test('completeTracing', async () => {
    traceGatherer.finishTracing = jest.fn()
    await traceGatherer.completeTracing()
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingComplete', expect.any(Object))
})

test('completeTracing: in failure case', async () => {
    traceGatherer.finishTracing = jest.fn()
    pageMock.tracing.stop.mockReturnValue(Promise.reject(new Error('boom')))
    await traceGatherer.completeTracing()
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingError', expect.any(Error))
})

test('onLoadEventFired', async () => {
    await traceGatherer.onLoadEventFired()
    traceGatherer.traceStart = Date.now() - 20000
    traceGatherer.completeTracing = jest.fn().mockReturnValue(Promise.resolve('yeahh'))

    traceGatherer.waitForNetworkIdleEvent = {
        promise: new Promise((resolve) => setTimeout(resolve, 50)),
        cancel: jest.fn()
    }
    traceGatherer.waitForCPUIdleEvent = {
        promise: new Promise((resolve) => setTimeout(resolve, 100)),
        cancel: jest.fn()
    }
    traceGatherer.waitForMaxTimeout = jest.fn().mockReturnValue(new Promise(
        (resolve) => setTimeout(resolve, 150)
    ))

    await traceGatherer.onLoadEventFired()
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.completeTracing).toHaveBeenCalledTimes(1)
})

test('onLoadEventFired: using min trace time', async () => {
    traceGatherer.traceStart = Date.now() - 9700
    traceGatherer.completeTracing = jest.fn().mockReturnValue(Promise.resolve('yeahh'))

    traceGatherer.waitForNetworkIdleEvent = {
        promise: new Promise((resolve) => setTimeout(resolve, 50)),
        cancel: jest.fn()
    }
    traceGatherer.waitForCPUIdleEvent = {
        promise: new Promise((resolve) => setTimeout(resolve, 100)),
        cancel: jest.fn()
    }
    traceGatherer.waitForMaxTimeout = jest.fn().mockReturnValue(new Promise(
        (resolve) => setTimeout(() => resolve(jest.fn()), 150)
    ))

    const start = Date.now()
    await traceGatherer.onLoadEventFired()
    expect(Date.now() - start).toBeGreaterThanOrEqual(250)
})

test('waitForMaxTimeout', async () => {
    const start = Date.now()
    traceGatherer.completeTracing = jest.fn()
    const done = await traceGatherer.waitForMaxTimeout(200)
    done()

    expect(Date.now() - start).toBeGreaterThan(198)
    expect(traceGatherer.completeTracing).toHaveBeenCalledTimes(1)
})
<commit_msg>mock timers in trace gather tests<commit_after>import TraceGatherer from '../../src/gatherer/trace'
import { FRAME_LOAD_START_TIMEOUT } from '../../src/constants'

import TRACELOG from '../__fixtures__/tracelog.json'

let traceGatherer

const pageMock = {
    on: jest.fn(),
    tracing: {
        start: jest.fn(),
        stop: jest.fn().mockReturnValue(Promise.resolve(Buffer.from(JSON.stringify(TRACELOG))))
    },
    evaluateOnNewDocument: jest.fn()
}
const sessionMock = {
    on: jest.fn(),
    removeListener: jest.fn()
}
const driverMock = {
    getCDPSession: jest.fn().mockReturnValue(Promise.resolve(sessionMock)),
    getActivePage: jest.fn().mockReturnValue(Promise.resolve(pageMock))
}

const frame = {
    id: 123,
    loaderId: 'foobar123',
    url: 'http://foobar.com'
}

jest.useFakeTimers()

beforeEach(() => {
    driverMock.getCDPSession.mockClear()
    driverMock.getActivePage.mockClear()
    pageMock.on.mockClear()
    pageMock.tracing.start.mockClear()
    pageMock.tracing.stop.mockClear()
    pageMock.evaluateOnNewDocument.mockClear()
    sessionMock.on.mockClear()
    traceGatherer = new TraceGatherer(driverMock)
    traceGatherer.emit = jest.fn()
})

test('should register eventlisteners for network monitor', () => {
    traceGatherer.networkStatusMonitor = { dispatch: jest.fn() }
    for (const fn of Object.values(traceGatherer.networkListeners)) {
        fn({ some: 'params' })
    }
    expect(traceGatherer.networkStatusMonitor.dispatch.mock.calls)
        .toMatchSnapshot()
})

test('onFrameLoadFail', () => {
    traceGatherer.onFrameLoadFail({ frame: () => ({ _id: 123 }) })
    expect(traceGatherer.failingFrameLoadIds).toEqual([123])
})

test('getter isTracing', () => {
    expect(traceGatherer.isTracing).toBe(false)

    traceGatherer.traceStart = Date.now()
    expect(traceGatherer.isTracing).toBe(true)
})

test('finishTracing', async () => {
    traceGatherer.pageLoadDetected = true
    traceGatherer.networkStatusMonitor = true
    traceGatherer.traceStart = true
    traceGatherer.frameId = true
    traceGatherer.loaderId = true
    traceGatherer.pageUrl = true
    traceGatherer.waitForNetworkIdleEvent = { cancel: jest.fn() }
    traceGatherer.waitForCPUIdleEvent = { cancel: jest.fn() }

    traceGatherer.finishTracing()
    await new Promise((resolve) => process.nextTick(resolve))

    expect(traceGatherer.pageLoadDetected).toBe(false)
    expect(traceGatherer.networkStatusMonitor).toBe(undefined)
    expect(traceGatherer.traceStart).toBe(undefined)
    expect(traceGatherer.frameId).toBe(undefined)
    expect(traceGatherer.loaderId).toBe(undefined)
    expect(traceGatherer.pageUrl).toBe(undefined)

    expect(sessionMock.removeListener).toHaveBeenCalledTimes(7)
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingFinished')
})

test('onFrameNavigated', () => {
    expect(traceGatherer.frameId).toBe(undefined)
    expect(traceGatherer.loaderId).toBe(undefined)
    expect(traceGatherer.pageUrl).toBe(undefined)
    traceGatherer.traceStart = Date.now()

    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toBeCalledWith('tracingStarted', 123)

    expect(traceGatherer.frameId).toBe(123)
    expect(traceGatherer.loaderId).toBe('foobar123')
    expect(traceGatherer.pageUrl).toBe('http://foobar.com')
})

test('onFrameNavigated: should not start if frame was detected', () => {
    traceGatherer.frameId = 123
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if a subframe was detected', () => {
    const subFrame = Object.assign({}, frame, { parentId: 122 })
    traceGatherer.onFrameNavigated({ frame: subFrame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if url is not supported', () => {
    const subFrame = Object.assign({}, frame, { url: 'data:,foobar' })
    traceGatherer.onFrameNavigated({ frame: subFrame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if tracing is not started', () => {
    traceGatherer.traceStart = undefined
    traceGatherer.finishTracing = jest.fn()
    traceGatherer.failingFrameLoadIds.push(123)
    traceGatherer.waitForNetworkIdleEvent = { cancel: jest.fn() }
    traceGatherer.waitForCPUIdleEvent = { cancel: jest.fn() }
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(0)
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(0)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should cancel trace if page load failed', () => {
    traceGatherer.traceStart = Date.now()
    traceGatherer.finishTracing = jest.fn()
    traceGatherer.failingFrameLoadIds.push(123)
    traceGatherer.waitForNetworkIdleEvent = { cancel: jest.fn() }
    traceGatherer.waitForCPUIdleEvent = { cancel: jest.fn() }
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.emit).toHaveBeenCalledWith('tracingError', expect.any(Error))
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(1)
})

test('onFrameNavigated: can detect page load', () => {
    traceGatherer.clickTraceTimeout = true
    traceGatherer.traceStart = Date.now()

    expect(traceGatherer.pageLoadDetected).toBe(false)
    traceGatherer.onFrameNavigated({ frame })
    expect(traceGatherer.pageLoadDetected).toBe(true)
})

test('startTracing', async () => {
    traceGatherer.waitForNetworkIdle = jest.fn()
    traceGatherer.waitForCPUIdle = jest.fn()
    await traceGatherer.startTracing('http://foobar.com')

    expect(sessionMock.on).toHaveBeenCalledTimes(7)
    expect(pageMock.tracing.start).toHaveBeenCalledTimes(1)
    expect(pageMock.evaluateOnNewDocument).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForNetworkIdle).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdle).toHaveBeenCalledTimes(1)
})

test('startTracing: registers timeout for click events', async () => {
    traceGatherer.waitForNetworkIdle = jest.fn()
    traceGatherer.waitForCPUIdle = jest.fn()
    traceGatherer.finishTracing = jest.fn()

    await traceGatherer.startTracing('click event')
    jest.advanceTimersByTime(FRAME_LOAD_START_TIMEOUT + 10)
    expect(pageMock.tracing.stop).toHaveBeenCalledTimes(1)
    await new Promise((resolve) => process.nextTick(resolve))
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
})

test('completeTracing', async () => {
    traceGatherer.finishTracing = jest.fn()
    await traceGatherer.completeTracing()
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingComplete', expect.any(Object))
})

test('completeTracing: in failure case', async () => {
    traceGatherer.finishTracing = jest.fn()
    pageMock.tracing.stop.mockReturnValue(Promise.reject(new Error('boom')))
    await traceGatherer.completeTracing()
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingError', expect.any(Error))
})

test('onLoadEventFired', async () => {
    await traceGatherer.onLoadEventFired()
    traceGatherer.traceStart = Date.now()
    traceGatherer.completeTracing = jest.fn().mockReturnValue(Promise.resolve('yeahh'))

    traceGatherer.waitForNetworkIdleEvent = {
        promise: new Promise((resolve) => {
            jest.advanceTimersByTime(50)
            resolve()
        }),
        cancel: jest.fn()
    }
    traceGatherer.waitForCPUIdleEvent = {
        promise: new Promise((resolve) => {
            jest.advanceTimersByTime(100)
            resolve()
        }),
        cancel: jest.fn()
    }

    process.nextTick(() => jest.advanceTimersByTime(10000))
    await traceGatherer.onLoadEventFired()
    expect(traceGatherer.waitForNetworkIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.waitForCPUIdleEvent.cancel).toHaveBeenCalledTimes(1)
    expect(traceGatherer.completeTracing).toHaveBeenCalledTimes(1)
})

test('onLoadEventFired: using min trace time', async () => {
    traceGatherer.traceStart = Date.now() - 9700
    traceGatherer.completeTracing = jest.fn().mockReturnValue(Promise.resolve('yeahh'))

    const doneCb = jest.fn()
    traceGatherer.waitForNetworkIdleEvent = {
        promise: new Promise((resolve) => {
            jest.advanceTimersByTime(50)
            resolve()
        }),
        cancel: jest.fn()
    }
    traceGatherer.waitForCPUIdleEvent = {
        promise: new Promise((resolve) => {
            jest.advanceTimersByTime(10)
            resolve()
        }),
        cancel: jest.fn()
    }
    traceGatherer.waitForMaxTimeout = jest.fn().mockReturnValue(new Promise((resolve) => {
        jest.advanceTimersByTime(150)
        resolve(doneCb)
    }))

    await traceGatherer.onLoadEventFired()
    expect(doneCb).toBeCalledTimes(1)
})

test('waitForMaxTimeout', async () => {
    traceGatherer.completeTracing = jest.fn()
    process.nextTick(() => jest.advanceTimersByTime(200))
    const done = await traceGatherer.waitForMaxTimeout(200)
    done()
    expect(traceGatherer.completeTracing).toHaveBeenCalledTimes(1)
})
<|endoftext|>


<commit_before>// @remove-on-eject-begin
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
// @remove-on-eject-end

var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
var getClientEnvironment = require('./env');
var paths = require('./paths');

// @remove-on-eject-begin
// `path` is not used after eject - see https://github.com/facebookincubator/create-react-app/issues/1174
var path = require('path');
// @remove-on-eject-end

// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
var publicPath = '/';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
var publicUrl = '';
// Get environment variables to inject into our app.
var env = getClientEnvironment(publicUrl);

// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
// The production configuration is different and lives in a separate file.
module.exports = {
  // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
  // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
  devtool: 'cheap-module-source-map',
  // These are the "entry points" to our application.
  // This means they will be the "root" imports that are included in JS bundle.
  // The first two entry points enable "hot" CSS and auto-refreshes for JS.
  entry: [
    // Include an alternative client for WebpackDevServer. A client's job is to
    // connect to WebpackDevServer by a socket and get notified about changes.
    // When you save a file, the client will either apply hot updates (in case
    // of CSS changes), or refresh the page (in case of JS changes). When you
    // make a syntax error, this client will display a syntax error overlay.
    // Note: instead of the default WebpackDevServer client, we use a custom one
    // to bring better experience for Create React App users. You can replace
    // the line below with these two lines if you prefer the stock client:
    // require.resolve('webpack-dev-server/client') + '?/',
    // require.resolve('webpack/hot/dev-server'),
    require.resolve('react-dev-utils/webpackHotDevClient'),
    // We ship a few polyfills by default:
    require.resolve('./polyfills'),
    // Finally, this is your app's code:
    paths.appIndexJs
    // We include the app code last so that if there is a runtime error during
    // initialization, it doesn't blow up the WebpackDevServer client, and
    // changing JS code would still trigger a refresh.
  ],
  output: {
    // Next line is not used in dev but WebpackDevServer crashes without it:
    path: paths.appBuild,
    // Add /* filename */ comments to generated require()s in the output.
    pathinfo: true,
    // This does not produce a real file. It's just the virtual path that is
    // served by WebpackDevServer in development. This is the JS bundle
    // containing code from all our entry points, and the Webpack runtime.
    filename: 'static/js/bundle.js',
    // This is the URL that app is served from. We use "/" in development.
    publicPath: publicPath
  },
  resolve: {
    // This allows you to set a fallback for where Webpack should look for modules.
    // We read `NODE_PATH` environment variable in `paths.js` and pass paths here.
    // We placed these paths second because we want `node_modules` to "win"
    // if there are any conflicts. This matches Node resolution mechanism.
    // https://github.com/facebookincubator/create-react-app/issues/253
    modules: ['node_modules'].concat(paths.nodePaths),
    // These are the reasonable defaults supported by the Node ecosystem.
    // We also include JSX as a common component filename extension to support
    // some tools, although we do not recommend using it, see:
    // https://github.com/facebookincubator/create-react-app/issues/290
    extensions: ['.js', '.json', '.jsx'],
    alias: {
      // Support React Native Web
      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
      'react-native': 'react-native-web'
    }
  },
  // @remove-on-eject-begin
  // Resolve loaders (webpack plugins for CSS, images, transpilation) from the
  // directory of `react-scripts` itself rather than the project directory.
  resolveLoader: {
    modules: [
      paths.ownNodeModules,
      // Lerna hoists everything, so we need to look in our app directory
      paths.appNodeModules
    ]
  },
  // @remove-on-eject-end
  module: {
    rules: [
      // Disable require.ensure as it's not a standard language feature.
      { parser: { requireEnsure: false } },
      // First, run the linter.
      // It's important to do this before Babel processes the JS.
      {
        test: /\.(js|jsx)$/,
        enforce: 'pre',
        use: [{
          options: {
            // @remove-on-eject-begin
            // Point ESLint to our predefined config.
            configFile: path.join(__dirname, '../.eslintrc'),
            useEslintrc: false,
            // @remove-on-eject-end
            cache: true
          },
          loader: 'eslint-loader'
        }],
        include: paths.appSrc
      },
      // ** ADDING/UPDATING LOADERS **
      // The "url" loader handles all assets unless explicitly excluded.
      // The `exclude` list *must* be updated with every change to loader extensions.
      // When adding a new loader, you must add its `test`
      // as a new entry in the `exclude` list for "url" loader.

      // "url" loader embeds assets smaller than specified size as data URLs to avoid requests.
      // Otherwise, it acts like the "file" loader.
      {
        exclude: [
          /\.html$/,
          /\.(js|jsx)$/,
          /\.css$/,
          /\.json$/,
          /\.svg$/
        ],
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/media/[name].[hash:8].[ext]'
        }
      },
      // Process JS with Babel.
      {
        test: /\.(js|jsx)$/,
        include: paths.appSrc,
        loader: 'babel-loader',
        options: {
          // @remove-on-eject-begin
          babelrc: false,
          presets: [require.resolve('babel-preset-react-app')],
          // @remove-on-eject-end
          // This is a feature of `babel-loader` for webpack (not Babel itself).
          // It enables caching results in ./node_modules/.cache/babel-loader/
          // directory for faster rebuilds.
          cacheDirectory: true
        }
      },
      // "postcss" loader applies autoprefixer to our CSS.
      // "css" loader resolves paths in CSS and adds assets as dependencies.
      // "style" loader turns CSS into JS modules that inject <style> tags.
      // In production, we use a plugin to extract that CSS to a file, but
      // in development "style" loader enables hot editing of CSS.
      {
        test: /\.css$/,
        use: [
          'style-loader', {
            loader: 'css-loader',
            options: {
              importLoaders: 1
            }
          }, {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss', // https://webpack.js.org/guides/migrating/#complex-options
              plugins: function () {
                return [
                  autoprefixer({
                    browsers: [
                      '>1%',
                      'last 4 versions',
                      'Firefox ESR',
                      'not ie < 9', // React doesn't support IE8 anyway
                    ]
                  })
                ]
              }
            }
          }
        ]
      },
      // "file" loader for svg
      {
        test: /\.svg$/,
        loader: 'file-loader',
        options: {
          name: 'static/media/[name].[hash:8].[ext]'
        }
      }
      // ** STOP ** Are you adding a new loader?
      // Remember to add the new extension(s) to the "url" loader exclusion list.
    ]
  },
  plugins: [
    // Makes some environment variables available in index.html.
    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In development, this will be an empty string.
    new InterpolateHtmlPlugin(env.raw),
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
    }),
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
    new webpack.DefinePlugin(env.stringified),
    // This is necessary to emit hot updates (currently CSS only):
    new webpack.HotModuleReplacementPlugin(),
    // Watcher doesn't work well if you mistype casing in a path so we use
    // a plugin that prints an error when you attempt to do this.
    // See https://github.com/facebookincubator/create-react-app/issues/240
    new CaseSensitivePathsPlugin(),
    // If you require a missing module and then `npm install` it, you still have
    // to restart the development server for Webpack to discover it. This plugin
    // makes the discovery automatic so you don't have to restart.
    // See https://github.com/facebookincubator/create-react-app/issues/186
    new WatchMissingNodeModulesPlugin(paths.appNodeModules)
  ],
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  },
  // Turn off performance hints during development because we don't do any
  // splitting or minification in interest of speed. These warnings become
  // cumbersome.
  performance: {
    hints: false
  }
};
<commit_msg>Revert "Enable eslint caching in development" (#1665)<commit_after>// @remove-on-eject-begin
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
// @remove-on-eject-end

var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
var getClientEnvironment = require('./env');
var paths = require('./paths');

// @remove-on-eject-begin
// `path` is not used after eject - see https://github.com/facebookincubator/create-react-app/issues/1174
var path = require('path');
// @remove-on-eject-end

// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
var publicPath = '/';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
var publicUrl = '';
// Get environment variables to inject into our app.
var env = getClientEnvironment(publicUrl);

// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
// The production configuration is different and lives in a separate file.
module.exports = {
  // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
  // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
  devtool: 'cheap-module-source-map',
  // These are the "entry points" to our application.
  // This means they will be the "root" imports that are included in JS bundle.
  // The first two entry points enable "hot" CSS and auto-refreshes for JS.
  entry: [
    // Include an alternative client for WebpackDevServer. A client's job is to
    // connect to WebpackDevServer by a socket and get notified about changes.
    // When you save a file, the client will either apply hot updates (in case
    // of CSS changes), or refresh the page (in case of JS changes). When you
    // make a syntax error, this client will display a syntax error overlay.
    // Note: instead of the default WebpackDevServer client, we use a custom one
    // to bring better experience for Create React App users. You can replace
    // the line below with these two lines if you prefer the stock client:
    // require.resolve('webpack-dev-server/client') + '?/',
    // require.resolve('webpack/hot/dev-server'),
    require.resolve('react-dev-utils/webpackHotDevClient'),
    // We ship a few polyfills by default:
    require.resolve('./polyfills'),
    // Finally, this is your app's code:
    paths.appIndexJs
    // We include the app code last so that if there is a runtime error during
    // initialization, it doesn't blow up the WebpackDevServer client, and
    // changing JS code would still trigger a refresh.
  ],
  output: {
    // Next line is not used in dev but WebpackDevServer crashes without it:
    path: paths.appBuild,
    // Add /* filename */ comments to generated require()s in the output.
    pathinfo: true,
    // This does not produce a real file. It's just the virtual path that is
    // served by WebpackDevServer in development. This is the JS bundle
    // containing code from all our entry points, and the Webpack runtime.
    filename: 'static/js/bundle.js',
    // This is the URL that app is served from. We use "/" in development.
    publicPath: publicPath
  },
  resolve: {
    // This allows you to set a fallback for where Webpack should look for modules.
    // We read `NODE_PATH` environment variable in `paths.js` and pass paths here.
    // We placed these paths second because we want `node_modules` to "win"
    // if there are any conflicts. This matches Node resolution mechanism.
    // https://github.com/facebookincubator/create-react-app/issues/253
    modules: ['node_modules'].concat(paths.nodePaths),
    // These are the reasonable defaults supported by the Node ecosystem.
    // We also include JSX as a common component filename extension to support
    // some tools, although we do not recommend using it, see:
    // https://github.com/facebookincubator/create-react-app/issues/290
    extensions: ['.js', '.json', '.jsx'],
    alias: {
      // Support React Native Web
      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
      'react-native': 'react-native-web'
    }
  },
  // @remove-on-eject-begin
  // Resolve loaders (webpack plugins for CSS, images, transpilation) from the
  // directory of `react-scripts` itself rather than the project directory.
  resolveLoader: {
    modules: [
      paths.ownNodeModules,
      // Lerna hoists everything, so we need to look in our app directory
      paths.appNodeModules
    ]
  },
  // @remove-on-eject-end
  module: {
    rules: [
      // Disable require.ensure as it's not a standard language feature.
      { parser: { requireEnsure: false } },
      // First, run the linter.
      // It's important to do this before Babel processes the JS.
      {
        test: /\.(js|jsx)$/,
        enforce: 'pre',
        use: [{
          // @remove-on-eject-begin
          // Point ESLint to our predefined config.
          options: {
            configFile: path.join(__dirname, '../.eslintrc'),
            useEslintrc: false
          },
          // @remove-on-eject-end
          loader: 'eslint-loader'
        }],
        include: paths.appSrc
      },
      // ** ADDING/UPDATING LOADERS **
      // The "url" loader handles all assets unless explicitly excluded.
      // The `exclude` list *must* be updated with every change to loader extensions.
      // When adding a new loader, you must add its `test`
      // as a new entry in the `exclude` list for "url" loader.

      // "url" loader embeds assets smaller than specified size as data URLs to avoid requests.
      // Otherwise, it acts like the "file" loader.
      {
        exclude: [
          /\.html$/,
          /\.(js|jsx)$/,
          /\.css$/,
          /\.json$/,
          /\.svg$/
        ],
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/media/[name].[hash:8].[ext]'
        }
      },
      // Process JS with Babel.
      {
        test: /\.(js|jsx)$/,
        include: paths.appSrc,
        loader: 'babel-loader',
        options: {
          // @remove-on-eject-begin
          babelrc: false,
          presets: [require.resolve('babel-preset-react-app')],
          // @remove-on-eject-end
          // This is a feature of `babel-loader` for webpack (not Babel itself).
          // It enables caching results in ./node_modules/.cache/babel-loader/
          // directory for faster rebuilds.
          cacheDirectory: true
        }
      },
      // "postcss" loader applies autoprefixer to our CSS.
      // "css" loader resolves paths in CSS and adds assets as dependencies.
      // "style" loader turns CSS into JS modules that inject <style> tags.
      // In production, we use a plugin to extract that CSS to a file, but
      // in development "style" loader enables hot editing of CSS.
      {
        test: /\.css$/,
        use: [
          'style-loader', {
            loader: 'css-loader',
            options: {
              importLoaders: 1
            }
          }, {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss', // https://webpack.js.org/guides/migrating/#complex-options
              plugins: function () {
                return [
                  autoprefixer({
                    browsers: [
                      '>1%',
                      'last 4 versions',
                      'Firefox ESR',
                      'not ie < 9', // React doesn't support IE8 anyway
                    ]
                  })
                ]
              }
            }
          }
        ]
      },
      // "file" loader for svg
      {
        test: /\.svg$/,
        loader: 'file-loader',
        options: {
          name: 'static/media/[name].[hash:8].[ext]'
        }
      }
      // ** STOP ** Are you adding a new loader?
      // Remember to add the new extension(s) to the "url" loader exclusion list.
    ]
  },
  plugins: [
    // Makes some environment variables available in index.html.
    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In development, this will be an empty string.
    new InterpolateHtmlPlugin(env.raw),
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
    }),
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
    new webpack.DefinePlugin(env.stringified),
    // This is necessary to emit hot updates (currently CSS only):
    new webpack.HotModuleReplacementPlugin(),
    // Watcher doesn't work well if you mistype casing in a path so we use
    // a plugin that prints an error when you attempt to do this.
    // See https://github.com/facebookincubator/create-react-app/issues/240
    new CaseSensitivePathsPlugin(),
    // If you require a missing module and then `npm install` it, you still have
    // to restart the development server for Webpack to discover it. This plugin
    // makes the discovery automatic so you don't have to restart.
    // See https://github.com/facebookincubator/create-react-app/issues/186
    new WatchMissingNodeModulesPlugin(paths.appNodeModules)
  ],
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  },
  // Turn off performance hints during development because we don't do any
  // splitting or minification in interest of speed. These warnings become
  // cumbersome.
  performance: {
    hints: false
  }
};
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');
const mongoose = require('mongoose');

// Utils
const {
  models: { getValuePrimaryKey },
} = require('strapi-utils');

const getModel = function(model, plugin) {
  return (
    _.get(strapi.plugins, [plugin, 'models', model]) ||
    _.get(strapi, ['models', model]) ||
    undefined
  );
};

const transformToArrayID = (array, pk) => {
  if (_.isArray(array)) {
    return array
      .map(value => value && (getValuePrimaryKey(value, pk) || value))
      .filter(n => n)
      .map(val => _.toString(val));
  }

  return transformToArrayID([array]);
};

const removeUndefinedKeys = (obj = {}) => _.pickBy(obj, _.negate(_.isUndefined));

const addRelationMorph = async (model, params) => {
  const { id, alias, refId, ref, field, filter } = params;

  await model.updateMany(
    {
      [model.primaryKey]: id,
    },
    {
      $push: {
        [alias]: {
          ref: new mongoose.Types.ObjectId(refId),
          kind: ref,
          [filter]: field,
        },
      },
    }
  );
};

const removeRelationMorph = async (model, params) => {
  const { alias } = params;

  let opts;
  // if entry id is provided simply query it
  if (params.id) {
    opts = {
      _id: params.id,
    };
  } else {
    opts = {
      [alias]: {
        $elemMatch: {
          ref: params.refId,
          kind: params.ref,
          [params.filter]: params.field,
        },
      },
    };
  }

  await model.updateMany(opts, {
    $pull: {
      [alias]: {
        ref: params.refId,
        kind: params.ref,
        [params.filter]: params.field,
      },
    },
  });
};

module.exports = {
  async update(params) {
    const relationUpdates = [];
    const populate = this.associations.map(x => x.alias);
    const primaryKeyValue = getValuePrimaryKey(params, this.primaryKey);

    const entry = await this.findOne({ [this.primaryKey]: primaryKeyValue })
      .populate(populate)
      .lean();

    // Only update fields which are on this document.
    const values = Object.keys(removeUndefinedKeys(params.values)).reduce((acc, attribute) => {
      const currentValue = entry[attribute];
      const newValue = params.values[attribute];

      const association = this.associations.find(x => x.alias === attribute);

      const details = this._attributes[attribute];

      // set simple attributes
      if (!association && _.get(details, 'isVirtual') !== true) {
        return _.set(acc, attribute, newValue);
      }

      const assocModel = getModel(details.model || details.collection, details.plugin);

      switch (association.nature) {
        case 'oneWay': {
          return _.set(acc, attribute, _.get(newValue, assocModel.primaryKey, newValue));
        }
        case 'oneToOne': {
          // if value is the same don't do anything
          if (currentValue === newValue) return acc;

          // if the value is null, set field to null on both sides
          if (_.isNull(newValue)) {
            const updatePromise = assocModel.updateOne(
              {
                [assocModel.primaryKey]: getValuePrimaryKey(currentValue, assocModel.primaryKey),
              },
              { [details.via]: null }
            );

            relationUpdates.push(updatePromise);
            return _.set(acc, attribute, null);
          }

          // set old relations to null
          const updateLink = this.updateOne(
            { [attribute]: new mongoose.Types.ObjectId(newValue) },
            { [attribute]: null }
          ).then(() => {
            return assocModel.updateOne(
              {
                [this.primaryKey]: new mongoose.Types.ObjectId(newValue),
              },
              { [details.via]: primaryKeyValue }
            );
          });

          // set new relation
          relationUpdates.push(updateLink);
          return _.set(acc, attribute, newValue);
        }
        case 'oneToMany': {
          // set relation to null for all the ids not in the list
          const attributeIds = currentValue;
          const toRemove = _.differenceWith(attributeIds, newValue, (a, b) => {
            return `${a[assocModel.primaryKey] || a}` === `${b[assocModel.primaryKey] || b}`;
          });

          const updatePromise = assocModel
            .updateMany(
              {
                [assocModel.primaryKey]: {
                  $in: toRemove.map(
                    val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                  ),
                },
              },
              { [details.via]: null }
            )
            .then(() => {
              return assocModel.updateMany(
                {
                  [assocModel.primaryKey]: {
                    $in: newValue.map(
                      val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                    ),
                  },
                },
                { [details.via]: primaryKeyValue }
              );
            });

          relationUpdates.push(updatePromise);
          return acc;
        }
        case 'manyToOne': {
          return _.set(acc, attribute, _.get(newValue, assocModel.primaryKey, newValue));
        }
        case 'manyWay':
        case 'manyToMany': {
          if (association.dominant) {
            return _.set(
              acc,
              attribute,
              newValue ? newValue.map(val => val[assocModel.primaryKey] || val) : newValue
            );
          }

          const updatePomise = assocModel
            .updateMany(
              {
                [assocModel.primaryKey]: {
                  $in: currentValue.map(
                    val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                  ),
                },
              },
              {
                $pull: {
                  [association.via]: new mongoose.Types.ObjectId(primaryKeyValue),
                },
              }
            )
            .then(() => {
              return assocModel.updateMany(
                {
                  [assocModel.primaryKey]: {
                    $in: newValue
                      ? newValue.map(
                          val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                        )
                      : newValue,
                  },
                },
                {
                  $addToSet: { [association.via]: [primaryKeyValue] },
                }
              );
            });

          relationUpdates.push(updatePomise);
          return acc;
        }
        // media -> model
        case 'manyMorphToMany':
        case 'manyMorphToOne': {
          newValue.forEach(obj => {
            const refModel = strapi.getModel(obj.ref, obj.source);

            const createRelation = () => {
              return addRelationMorph(this, {
                id: entry[this.primaryKey],
                alias: association.alias,
                ref: obj.kind || refModel.globalId,
                refId: new mongoose.Types.ObjectId(obj.refId),
                field: obj.field,
                filter: association.filter,
              });
            };

            // Clear relations to refModel
            const reverseAssoc = refModel.associations.find(assoc => assoc.alias === obj.field);
            if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
              relationUpdates.push(
                removeRelationMorph(this, {
                  alias: association.alias,
                  ref: obj.kind || refModel.globalId,
                  refId: new mongoose.Types.ObjectId(obj.refId),
                  field: obj.field,
                  filter: association.filter,
                })
                  .then(createRelation)
                  .then(() => {
                    // set field inside refModel
                    return refModel.updateMany(
                      {
                        [refModel.primaryKey]: new mongoose.Types.ObjectId(obj.refId),
                      },
                      {
                        [obj.field]: new mongoose.Types.ObjectId(entry[this.primaryKey]),
                      }
                    );
                  })
              );
            } else {
              relationUpdates.push(
                createRelation().then(() => {
                  // push to field inside refModel
                  return refModel.updateMany(
                    {
                      [refModel.primaryKey]: new mongoose.Types.ObjectId(obj.refId),
                    },
                    {
                      $push: { [obj.field]: new mongoose.Types.ObjectId(entry[this.primaryKey]) },
                    }
                  );
                })
              );
            }
          });
          break;
        }
        // model -> media
        case 'oneToManyMorph':
        case 'manyToManyMorph': {
          // Compare array of ID to find deleted files.
          const currentIds = transformToArrayID(currentValue, this.primaryKey);
          const newIds = transformToArrayID(newValue, this.primaryKey);

          const toAdd = _.difference(newIds, currentIds);
          const toRemove = _.difference(currentIds, newIds);

          const model = getModel(details.model || details.collection, details.plugin);

          _.set(acc, attribute, newIds);

          const addPromise = Promise.all(
            toAdd.map(id => {
              return addRelationMorph(model, {
                id,
                alias: association.via,
                ref: this.globalId,
                refId: entry._id,
                field: association.alias,
                filter: association.filter,
              });
            })
          );

          relationUpdates.push(addPromise);

          toRemove.forEach(id => {
            relationUpdates.push(
              removeRelationMorph(model, {
                id,
                alias: association.via,
                ref: this.globalId,
                refId: entry._id,
                field: association.alias,
                filter: association.filter,
              })
            );
          });
          break;
        }
        case 'oneMorphToOne':
        case 'oneMorphToMany':
          break;
        default:
      }

      return acc;
    }, {});

    // Update virtuals fields.
    await Promise.all(relationUpdates).then(() =>
      this.updateOne({ [this.primaryKey]: primaryKeyValue }, values, {
        strict: false,
      })
    );

    const updatedEntity = await this.findOne({
      [this.primaryKey]: primaryKeyValue,
    }).populate(populate);

    return updatedEntity && updatedEntity.toObject ? updatedEntity.toObject() : updatedEntity;
  },

  deleteRelations(entry) {
    const primaryKeyValue = entry[this.primaryKey];

    return Promise.all(
      this.associations.map(async association => {
        const { nature, via, dominant } = association;

        // TODO: delete all the ref to the model

        switch (nature) {
          case 'oneWay':
          case 'manyWay': {
            return;
          }
          case 'oneToMany':
          case 'oneToOne': {
            if (!via) {
              return;
            }

            const targetModel = strapi.db.getModel(
              association.model || association.collection,
              association.plugin
            );

            return targetModel.updateMany({ [via]: primaryKeyValue }, { [via]: null });
          }
          case 'manyToMany':
          case 'manyToOne': {
            if (!via || dominant) {
              return;
            }

            const targetModel = strapi.db.getModel(
              association.model || association.collection,
              association.plugin
            );

            return targetModel.updateMany(
              { [via]: primaryKeyValue },
              { $pull: { [via]: primaryKeyValue } }
            );
          }
          case 'oneToManyMorph':
          case 'manyToManyMorph': {
            // delete relation inside of the ref model

            const targetModel = strapi.db.getModel(
              association.model || association.collection,
              association.plugin
            );

            // ignore them ghost relations
            if (!targetModel) return;

            const element = {
              ref: primaryKeyValue,
              kind: this.globalId,
              [association.filter]: association.alias,
            };

            return targetModel.updateMany(
              { [via]: { $elemMatch: element } },
              { $pull: { [via]: element } }
            );
          }
          case 'manyMorphToMany':
          case 'manyMorphToOne': {
            // delete relation inside of the ref model
            // console.log(entry[association.alias]);

            if (Array.isArray(entry[association.alias])) {
              return Promise.all(
                entry[association.alias].map(val => {
                  const targetModel = strapi.db.getModelByGlobalId(val.kind);

                  // ignore them ghost relations
                  if (!targetModel) return;

                  const field = val[association.filter];
                  const reverseAssoc = targetModel.associations.find(
                    assoc => assoc.alias === field
                  );

                  if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
                    return targetModel.updateMany(
                      {
                        [targetModel.primaryKey]: val.ref && (val.ref._id || val.ref),
                      },
                      {
                        [field]: null,
                      }
                    );
                  }

                  return targetModel.updateMany(
                    {
                      [targetModel.primaryKey]: val.ref && (val.ref._id || val.ref),
                    },
                    {
                      $pull: { [field]: primaryKeyValue },
                    }
                  );
                })
              );
            }

            return;
          }
          case 'oneMorphToOne':
          case 'oneMorphToMany': {
            return;
          }
        }
      })
    );
  },
};
<commit_msg>Fix mongo error trying to cast from empty array (#6358)<commit_after>/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');
const mongoose = require('mongoose');

// Utils
const {
  models: { getValuePrimaryKey },
} = require('strapi-utils');

const getModel = function(model, plugin) {
  return (
    _.get(strapi.plugins, [plugin, 'models', model]) ||
    _.get(strapi, ['models', model]) ||
    undefined
  );
};

const transformToArrayID = (array, pk) => {
  if (_.isArray(array)) {
    return array
      .map(value => value && (getValuePrimaryKey(value, pk) || value))
      .filter(n => n)
      .map(val => _.toString(val));
  }

  return transformToArrayID([array]);
};

const removeUndefinedKeys = (obj = {}) => _.pickBy(obj, _.negate(_.isUndefined));

const addRelationMorph = async (model, params) => {
  const { id, alias, refId, ref, field, filter } = params;

  await model.updateMany(
    {
      [model.primaryKey]: id,
    },
    {
      $push: {
        [alias]: {
          ref: new mongoose.Types.ObjectId(refId),
          kind: ref,
          [filter]: field,
        },
      },
    }
  );
};

const removeRelationMorph = async (model, params) => {
  const { alias } = params;

  let opts;
  // if entry id is provided simply query it
  if (params.id) {
    opts = {
      _id: params.id,
    };
  } else {
    opts = {
      [alias]: {
        $elemMatch: {
          ref: params.refId,
          kind: params.ref,
          [params.filter]: params.field,
        },
      },
    };
  }

  await model.updateMany(opts, {
    $pull: {
      [alias]: {
        ref: params.refId,
        kind: params.ref,
        [params.filter]: params.field,
      },
    },
  });
};

module.exports = {
  async update(params) {
    const relationUpdates = [];
    const populate = this.associations.map(x => x.alias);
    const primaryKeyValue = getValuePrimaryKey(params, this.primaryKey);

    const entry = await this.findOne({ [this.primaryKey]: primaryKeyValue })
      .populate(populate)
      .lean();

    // Only update fields which are on this document.
    const values = Object.keys(removeUndefinedKeys(params.values)).reduce((acc, attribute) => {
      const currentValue = entry[attribute];
      const newValue = params.values[attribute];

      const association = this.associations.find(x => x.alias === attribute);

      const details = this._attributes[attribute];

      // set simple attributes
      if (!association && _.get(details, 'isVirtual') !== true) {
        return _.set(acc, attribute, newValue);
      }

      const assocModel = getModel(details.model || details.collection, details.plugin);

      switch (association.nature) {
        case 'oneWay': {
          return _.set(acc, attribute, _.get(newValue, assocModel.primaryKey, newValue));
        }
        case 'oneToOne': {
          // if value is the same don't do anything
          if (currentValue === newValue) return acc;

          // if the value is null, set field to null on both sides
          if (_.isNull(newValue)) {
            const updatePromise = assocModel.updateOne(
              {
                [assocModel.primaryKey]: getValuePrimaryKey(currentValue, assocModel.primaryKey),
              },
              { [details.via]: null }
            );

            relationUpdates.push(updatePromise);
            return _.set(acc, attribute, null);
          }

          // set old relations to null
          const updateLink = this.updateOne(
            { [attribute]: new mongoose.Types.ObjectId(newValue) },
            { [attribute]: null }
          ).then(() => {
            return assocModel.updateOne(
              {
                [this.primaryKey]: new mongoose.Types.ObjectId(newValue),
              },
              { [details.via]: primaryKeyValue }
            );
          });

          // set new relation
          relationUpdates.push(updateLink);
          return _.set(acc, attribute, newValue);
        }
        case 'oneToMany': {
          // set relation to null for all the ids not in the list
          const attributeIds = currentValue;
          const toRemove = _.differenceWith(attributeIds, newValue, (a, b) => {
            return `${a[assocModel.primaryKey] || a}` === `${b[assocModel.primaryKey] || b}`;
          });

          const updatePromise = assocModel
            .updateMany(
              {
                [assocModel.primaryKey]: {
                  $in: toRemove.map(
                    val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                  ),
                },
              },
              { [details.via]: null }
            )
            .then(() => {
              return assocModel.updateMany(
                {
                  [assocModel.primaryKey]: {
                    $in: newValue.map(
                      val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                    ),
                  },
                },
                { [details.via]: primaryKeyValue }
              );
            });

          relationUpdates.push(updatePromise);
          return acc;
        }
        case 'manyToOne': {
          return _.set(acc, attribute, _.get(newValue, assocModel.primaryKey, newValue));
        }
        case 'manyWay':
        case 'manyToMany': {
          if (association.dominant) {
            return _.set(
              acc,
              attribute,
              newValue ? newValue.map(val => val[assocModel.primaryKey] || val) : newValue
            );
          }

          const updatePomise = assocModel
            .updateMany(
              {
                [assocModel.primaryKey]: {
                  $in: currentValue.map(
                    val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                  ),
                },
              },
              {
                $pull: {
                  [association.via]: new mongoose.Types.ObjectId(primaryKeyValue),
                },
              }
            )
            .then(() => {
              return assocModel.updateMany(
                {
                  [assocModel.primaryKey]: {
                    $in: newValue
                      ? newValue.map(
                          val => new mongoose.Types.ObjectId(val[assocModel.primaryKey] || val)
                        )
                      : newValue,
                  },
                },
                {
                  $addToSet: { [association.via]: [primaryKeyValue] },
                }
              );
            });

          relationUpdates.push(updatePomise);
          return acc;
        }
        // media -> model
        case 'manyMorphToMany':
        case 'manyMorphToOne': {
          newValue.forEach(obj => {
            const refModel = strapi.getModel(obj.ref, obj.source);

            const createRelation = () => {
              return addRelationMorph(this, {
                id: entry[this.primaryKey],
                alias: association.alias,
                ref: obj.kind || refModel.globalId,
                refId: new mongoose.Types.ObjectId(obj.refId),
                field: obj.field,
                filter: association.filter,
              });
            };

            // Clear relations to refModel
            const reverseAssoc = refModel.associations.find(assoc => assoc.alias === obj.field);
            if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
              relationUpdates.push(
                removeRelationMorph(this, {
                  alias: association.alias,
                  ref: obj.kind || refModel.globalId,
                  refId: new mongoose.Types.ObjectId(obj.refId),
                  field: obj.field,
                  filter: association.filter,
                })
                  .then(createRelation)
                  .then(() => {
                    // set field inside refModel
                    return refModel.updateMany(
                      {
                        [refModel.primaryKey]: new mongoose.Types.ObjectId(obj.refId),
                      },
                      {
                        [obj.field]: new mongoose.Types.ObjectId(entry[this.primaryKey]),
                      }
                    );
                  })
              );
            } else {
              relationUpdates.push(
                createRelation().then(() => {
                  // push to field inside refModel
                  return refModel.updateMany(
                    {
                      [refModel.primaryKey]: new mongoose.Types.ObjectId(obj.refId),
                    },
                    {
                      $push: { [obj.field]: new mongoose.Types.ObjectId(entry[this.primaryKey]) },
                    }
                  );
                })
              );
            }
          });
          break;
        }
        // model -> media
        case 'oneToManyMorph':
        case 'manyToManyMorph': {
          // Compare array of ID to find deleted files.
          const currentIds = transformToArrayID(currentValue, this.primaryKey);
          const newIds = transformToArrayID(newValue, this.primaryKey);

          const toAdd = _.difference(newIds, currentIds);
          const toRemove = _.difference(currentIds, newIds);

          const model = getModel(details.model || details.collection, details.plugin);

          if (!Array.isArray(newValue)) {
            _.set(acc, attribute, newIds[0]);
          } else {
            _.set(acc, attribute, newIds);
          }

          const addPromise = Promise.all(
            toAdd.map(id => {
              return addRelationMorph(model, {
                id,
                alias: association.via,
                ref: this.globalId,
                refId: entry._id,
                field: association.alias,
                filter: association.filter,
              });
            })
          );

          relationUpdates.push(addPromise);

          toRemove.forEach(id => {
            relationUpdates.push(
              removeRelationMorph(model, {
                id,
                alias: association.via,
                ref: this.globalId,
                refId: entry._id,
                field: association.alias,
                filter: association.filter,
              })
            );
          });
          break;
        }
        case 'oneMorphToOne':
        case 'oneMorphToMany':
          break;
        default:
      }

      return acc;
    }, {});

    // Update virtuals fields.
    await Promise.all(relationUpdates).then(() =>
      this.updateOne({ [this.primaryKey]: primaryKeyValue }, values, {
        strict: false,
      })
    );

    const updatedEntity = await this.findOne({
      [this.primaryKey]: primaryKeyValue,
    }).populate(populate);

    return updatedEntity && updatedEntity.toObject ? updatedEntity.toObject() : updatedEntity;
  },

  deleteRelations(entry) {
    const primaryKeyValue = entry[this.primaryKey];

    return Promise.all(
      this.associations.map(async association => {
        const { nature, via, dominant } = association;

        // TODO: delete all the ref to the model

        switch (nature) {
          case 'oneWay':
          case 'manyWay': {
            return;
          }
          case 'oneToMany':
          case 'oneToOne': {
            if (!via) {
              return;
            }

            const targetModel = strapi.db.getModel(
              association.model || association.collection,
              association.plugin
            );

            return targetModel.updateMany({ [via]: primaryKeyValue }, { [via]: null });
          }
          case 'manyToMany':
          case 'manyToOne': {
            if (!via || dominant) {
              return;
            }

            const targetModel = strapi.db.getModel(
              association.model || association.collection,
              association.plugin
            );

            return targetModel.updateMany(
              { [via]: primaryKeyValue },
              { $pull: { [via]: primaryKeyValue } }
            );
          }
          case 'oneToManyMorph':
          case 'manyToManyMorph': {
            // delete relation inside of the ref model

            const targetModel = strapi.db.getModel(
              association.model || association.collection,
              association.plugin
            );

            // ignore them ghost relations
            if (!targetModel) return;

            const element = {
              ref: primaryKeyValue,
              kind: this.globalId,
              [association.filter]: association.alias,
            };

            return targetModel.updateMany(
              { [via]: { $elemMatch: element } },
              { $pull: { [via]: element } }
            );
          }
          case 'manyMorphToMany':
          case 'manyMorphToOne': {
            // delete relation inside of the ref model
            // console.log(entry[association.alias]);

            if (Array.isArray(entry[association.alias])) {
              return Promise.all(
                entry[association.alias].map(val => {
                  const targetModel = strapi.db.getModelByGlobalId(val.kind);

                  // ignore them ghost relations
                  if (!targetModel) return;

                  const field = val[association.filter];
                  const reverseAssoc = targetModel.associations.find(
                    assoc => assoc.alias === field
                  );

                  if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
                    return targetModel.updateMany(
                      {
                        [targetModel.primaryKey]: val.ref && (val.ref._id || val.ref),
                      },
                      {
                        [field]: null,
                      }
                    );
                  }

                  return targetModel.updateMany(
                    {
                      [targetModel.primaryKey]: val.ref && (val.ref._id || val.ref),
                    },
                    {
                      $pull: { [field]: primaryKeyValue },
                    }
                  );
                })
              );
            }

            return;
          }
          case 'oneMorphToOne':
          case 'oneMorphToMany': {
            return;
          }
        }
      })
    );
  },
};
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { isFunction } from 'lodash';
import { Provider } from 'react-redux';
import * as appModule from '../redux/modules/veritoneApp';
import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';
import { modules, helpers } from 'veritone-redux-common';
import { VeritoneSDKThemeProvider } from 'veritone-react-common';

const { auth: authModule, config: configModule, user: userModule } = modules;
const { promiseMiddleware } = helpers;
const {
  WAIT_FOR_ACTION,
  ERROR_ACTION,
  CALLBACK_ERROR_ARGUMENT
} = promiseMiddleware;

class _VeritoneApp {
  _store = configureStore();
  _containerEl = null;

  constructor(config) {
    this._store.dispatch(configModule.setConfig({ ...appConfig, ...config }));
    this._theme = config && config.theme;
  }

  _register(widget) {
    this._store.dispatch(appModule.widgetAdded(widget));
    this._renderReactApp();
  }

  _unregister(widget) {
    this._store.dispatch(appModule.widgetRemoved(widget));
    this._renderReactApp();
  }

  login({ sessionToken, OAuthToken } = {}) {
    // Allows us to transform dispatch into a promise by adding symbols
    // See promiseMiddleware.js
    const addSymbols = action => {
      return {
        ...action,
        [WAIT_FOR_ACTION]: userModule.FETCH_USER_SUCCESS,
        [ERROR_ACTION]: userModule.FETCH_USER_FAILURE,
        [CALLBACK_ERROR_ARGUMENT]: action => action.payload
      };
    };

    if (sessionToken) {
      return this._store.dispatch(
        addSymbols(authModule.setSessionToken(sessionToken))
      );
    } else if (OAuthToken) {
      return this._store.dispatch(
        addSymbols(authModule.setOAuthToken(OAuthToken))
      );
    } else {
      return this._store.dispatch(addSymbols(authModule.checkAuthNoToken()));
    }
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  setWidgetRef = (widget, ref) => {
    if (!ref) {
      // protect against errors when destroying the app
      return;
    }

    if (isFunction(ref.getWrappedInstance) && !ref.wrappedInstance) {
      console.warn(
        `Warning: the following widget looks like it's wrapped with a
         @connect decorator, but the withRef option is not set to true.
         { withRef: true } should be set as the fourth argument to @connect`
      );
      console.warn(widget);

      return;
    }

    // try to get at the base component for @connected widgets.
    // fixme: generic solution (hoisting specified instance methods?)
    // https://github.com/elado/hoist-non-react-methods
    const r = isFunction(ref.getWrappedInstance)
      ? ref.getWrappedInstance()
      : ref;

    widget.setRefProperties(r);
  };

  _renderReactApp() {
    this._containerEl = document.getElementById('veritone-react-app');
    if (!this._containerEl) {
      this._containerEl = document.createElement('div');
      this._containerEl.setAttribute('id', 'veritone-react-app');
      document.body.appendChild(this._containerEl);
    }

    ReactDOM.render(
      <VeritoneSDKThemeProvider theme={this._theme}>
        <Provider store={this._store}>

            {appModule.widgets(this._store.getState()).map(w => {
              if (!w._elId) {
                console.warn(
                  'The widget',
                  w,
                  'needs to specify an elId that references an existing dom node.'
                );
                return null;
              }

              if (document.getElementById(w._elId)) {
                return ReactDOM.createPortal(
                  <w.Component
                    {...w.props}
                    // bind is OK because this isn't a component -- only renders
                    // when mount() is called.
                    // eslint-disable-next-line
                    ref={this.setWidgetRef.bind(this, w)}
                  />,
                  document.getElementById(w._elId)
                );
              }
            })}

        </Provider>
      </VeritoneSDKThemeProvider>,
      this._containerEl
    );
  }
}

let global = window || {};
export default function VeritoneApp(config, { _isWidget } = {}) {
  // client calls this on init to configure the app:
  // import VeritoneApp from 'veritone-widgets';
  // VeritoneApp({ ...myConfig })
  if (!global.__veritoneAppSingleton) {
    if (_isWidget) {
      console.warn(
        `A widget was registered to an app which hasn't yet been initialized. Import and call VeritoneApp before constructing any widgets.`
      );
      return;
    }

    global.__veritoneAppSingleton = new _VeritoneApp(config);
  }

  return global.__veritoneAppSingleton;
}
<commit_msg>add veritone-app changes from sdo-integration<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { isFunction } from 'lodash';
import { Provider } from 'react-redux';
import * as appModule from '../redux/modules/veritoneApp';
import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';
import { modules, util, helpers } from 'veritone-redux-common';
import { VeritoneSDKThemeProvider } from 'veritone-react-common';

const { auth: authModule, config: configModule, user: userModule } = modules;
const { promiseMiddleware } = helpers;
const {
  WAIT_FOR_ACTION,
  ERROR_ACTION,
  CALLBACK_ERROR_ARGUMENT
} = promiseMiddleware;

const Sagas = util.reactReduxSaga.Sagas;
class _VeritoneApp {
  _store = configureStore();
  _containerEl = null;

  constructor(config) {
    this._store.dispatch(configModule.setConfig({ ...appConfig, ...config }));
    this._theme = config && config.theme;
  }

  _register(widget) {
    this._store.dispatch(appModule.widgetAdded(widget));
    this._renderReactApp();
  }

  _unregister(widget) {
    this._store.dispatch(appModule.widgetRemoved(widget));
    this._renderReactApp();
  }

  login({ sessionToken, OAuthToken } = {}) {
    // Allows us to transform dispatch into a promise by adding symbols
    // See promiseMiddleware.js
    const addSymbols = action => {
      return {
        ...action,
        [WAIT_FOR_ACTION]: userModule.FETCH_USER_SUCCESS,
        [ERROR_ACTION]: userModule.FETCH_USER_FAILURE,
        [CALLBACK_ERROR_ARGUMENT]: action => action.payload
      };
    };

    if (sessionToken) {
      return this._store.dispatch(
        addSymbols(authModule.setSessionToken(sessionToken))
      );
    } else if (OAuthToken) {
      return this._store.dispatch(
        addSymbols(authModule.setOAuthToken(OAuthToken))
      );
    } else {
      return this._store.dispatch(addSymbols(authModule.checkAuthNoToken()));
    }
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  setWidgetRef = (widget, ref) => {
    if (!ref) {
      // protect against errors when destroying the app
      return;
    }

    if (isFunction(ref.getWrappedInstance) && !ref.wrappedInstance) {
      console.warn(
        `Warning: the following widget looks like it's wrapped with a
         @connect decorator, but the withRef option is not set to true.
         { withRef: true } should be set as the fourth argument to @connect`
      );
      console.warn(widget);

      return;
    }

    // try to get at the base component for @connected widgets.
    // fixme: generic solution (hoisting specified instance methods?)
    // https://github.com/elado/hoist-non-react-methods
    const r = isFunction(ref.getWrappedInstance)
      ? ref.getWrappedInstance()
      : ref;

    widget.setRefProperties(r);
  };

  _renderReactApp() {
    this._containerEl = document.getElementById('veritone-react-app');
    if (!this._containerEl) {
      this._containerEl = document.createElement('div');
      this._containerEl.setAttribute('id', 'veritone-react-app');
      document.body.appendChild(this._containerEl);
    }

    ReactDOM.render(
      <VeritoneSDKThemeProvider theme={this._theme}>
        <Sagas middleware={this._store.sagaMiddleware}>
          <Provider store={this._store}>

              {appModule.widgets(this._store.getState()).map(w => {
                if (!w._elId) {
                  console.warn(
                    'The widget',
                    w,
                    'needs to specify an elId that references an existing dom node.'
                  );
                  return null;
                }

                if (document.getElementById(w._elId)) {
                  return ReactDOM.createPortal(
                    <w.Component
                      {...w.props}
                      // bind is OK because this isn't a component -- only renders
                      // when mount() is called.
                      // eslint-disable-next-line
                      ref={this.setWidgetRef.bind(this, w)}
                    />,
                    document.getElementById(w._elId)
                  );
                }
              })}

          </Provider>
        </Sagas>
      </VeritoneSDKThemeProvider>,
      this._containerEl
    );
  }
}

let global = window || {};
export default function VeritoneApp(config, { _isWidget } = {}) {
  // client calls this on init to configure the app:
  // import VeritoneApp from 'veritone-widgets';
  // VeritoneApp({ ...myConfig })
  if (!global.__veritoneAppSingleton) {
    if (_isWidget) {
      console.warn(
        `A widget was registered to an app which hasn't yet been initialized. Import and call VeritoneApp before constructing any widgets.`
      );
      return;
    }

    global.__veritoneAppSingleton = new _VeritoneApp(config);
  }

  return global.__veritoneAppSingleton;
}<|endoftext|>


<commit_before>import json2csv from 'json2csv';
import React, { Component } from 'react';
import ReactDataGrid from 'react-data-grid';
import { observer as globalObserver } from 'binary-common-utils/lib/observer';
import { appendRow, updateRow, saveAs } from '../shared';
import { translate } from '../../../common/i18n';
import { roundBalance } from '../../common/tools';
import * as style from '../style';

const isNumber = num => num !== '' && Number.isFinite(Number(num));

const getProfit = ({ sell_price: sellPrice, buy_price: buyPrice, currency }) => {
    if (isNumber(sellPrice) && isNumber(buyPrice)) {
        return roundBalance({
            currency,
            balance: Number(sellPrice) - Number(buyPrice),
        });
    }
    return '';
};

const minHeight = 290;
const rowHeight = 25;

const ProfitColor = ({ value }) =>
 0 ? style.green : style.red}>{value}
;

export default class TradeTable extends Component {
    constructor({ accountID }) {
        super();
        this.state = {
            initial: {
                id  : 0,
                rows: [],
            },
            [accountID]: {
                id  : 0,
                rows: [],
            },
        };
        this.columns = [
            { key: 'timestamp', width: 150, resizable: true, name: translate('Timestamp') },
            { key: 'reference', width: 110, resizable: true, name: translate('Reference') },
            { key: 'contract_type', width: 80, resizable: true, name: translate('Trade type') },
            { key: 'entry_tick', width: 80, resizable: true, name: translate('Entry spot') },
            { key: 'exit_tick', width: 80, resizable: true, name: translate('Exit spot') },
            { key: 'sell_spot', width: 80, resizable: true, name: translate('Sell spot') },
            { key: 'buy_price', width: 80, resizable: true, name: translate('Buy price') },
            { key: 'profit', width: 80, resizable: true, name: translate('Profit/Loss'), formatter: ProfitColor },
        ];
    }
    componentWillMount() {
        globalObserver.register('summary.export', () => {
            const { accountID } = this.props;
            const { rows } = this.state[accountID];
            if (rows.length) this.export();
        });
        globalObserver.register('bot.contract', info => {
            if (!info) {
                return;
            }
            const buyDate = new Date(info.date_start * 1000);
            const timestamp = `${buyDate.toISOString().split('T')[0]} ${buyDate.toTimeString().slice(0, 8)} ${
                buyDate.toTimeString().split(' ')[1]
            }`;
            const tradeObj = { reference: info.transaction_ids.buy, ...info, timestamp };
            const { accountID } = tradeObj;

            const trade = {
                ...tradeObj,
                profit: getProfit(tradeObj),
            };

            const accountStat = this.getAccountStat(accountID);

            const { rows } = accountStat;
            const prevRowIndex = rows.findIndex(t => t.reference === trade.reference);

            if (trade.is_expired && trade.is_sold && !trade.exit_tick) trade.exit_tick = '-';

            if (prevRowIndex >= 0) {
                this.setState({ [accountID]: updateRow(prevRowIndex, trade, accountStat) });
            } else {
                this.setState({ [accountID]: appendRow(trade, accountStat) });
            }
        });
    }
    rowGetter(i) {
        const { accountID } = this.props;
        const { rows } = this.state[accountID];
        return rows[rows.length - 1 - i];
    }
    export() {
        const { accountID } = this.props;

        const data = json2csv({
            data  : this.state[accountID].rows,
            fields: [
                'id',
                'timestamp',
                'reference',
                'contract_type',
                'entry_tick',
                'exit_tick',
                'sell_spot',
                'buy_price',
                'sell_price',
                'profit',
            ],
        });
        saveAs({ data, filename: 'logs.csv', type: 'text/csv;charset=utf-8' });
    }
    getAccountStat(accountID) {
        if (!(accountID in this.state)) {
            const initialInfo = this.state.initial;
            this.setState({ [accountID]: { ...initialInfo } });
            return initialInfo;
        }
        return this.state[accountID];
    }
    render() {
        const { accountID } = this.props;
        const rows = accountID in this.state ? this.state[accountID].rows : [];
        return (

                <ReactDataGrid
                    columns={this.columns}
                    rowGetter={this.rowGetter.bind(this)}
                    rowsCount={rows.length}
                    minHeight={minHeight}
                    rowHeight={rowHeight}
                />

        );
    }
}
<commit_msg>Trade table default width adjustment<commit_after>import json2csv from 'json2csv';
import React, { Component } from 'react';
import ReactDataGrid from 'react-data-grid';
import { observer as globalObserver } from 'binary-common-utils/lib/observer';
import { appendRow, updateRow, saveAs } from '../shared';
import { translate } from '../../../common/i18n';
import { roundBalance } from '../../common/tools';
import * as style from '../style';

const isNumber = num => num !== '' && Number.isFinite(Number(num));

const getProfit = ({ sell_price: sellPrice, buy_price: buyPrice, currency }) => {
    if (isNumber(sellPrice) && isNumber(buyPrice)) {
        return roundBalance({
            currency,
            balance: Number(sellPrice) - Number(buyPrice),
        });
    }
    return '';
};

const minHeight = 290;
const rowHeight = 25;

const ProfitColor = ({ value }) =>
 0 ? style.green : style.red}>{value}
;

export default class TradeTable extends Component {
    constructor({ accountID }) {
        super();
        this.state = {
            initial: {
                id  : 0,
                rows: [],
            },
            [accountID]: {
                id  : 0,
                rows: [],
            },
        };
        this.columns = [
            { key: 'timestamp', width: 176, resizable: true, name: translate('Timestamp') },
            { key: 'reference', width: 87, resizable: true, name: translate('Reference') },
            { key: 'contract_type', width: 80, resizable: true, name: translate('Trade type') },
            { key: 'entry_tick', width: 80, resizable: true, name: translate('Entry spot') },
            { key: 'exit_tick', width: 70, resizable: true, name: translate('Exit spot') },
            { key: 'sell_spot', width: 80, resizable: true, name: translate('Sell spot') },
            { key: 'buy_price', width: 80, resizable: true, name: translate('Buy price') },
            { key: 'profit', width: 80, resizable: true, name: translate('Profit/Loss'), formatter: ProfitColor },
        ];
    }
    componentWillMount() {
        globalObserver.register('summary.export', () => {
            const { accountID } = this.props;
            const { rows } = this.state[accountID];
            if (rows.length) this.export();
        });
        globalObserver.register('bot.contract', info => {
            if (!info) {
                return;
            }
            const buyDate = new Date(info.date_start * 1000);
            const timestamp = `${buyDate.toISOString().split('T')[0]} ${buyDate.toTimeString().slice(0, 8)} ${
                buyDate.toTimeString().split(' ')[1]
            }`;
            const tradeObj = { reference: info.transaction_ids.buy, ...info, timestamp };
            const { accountID } = tradeObj;

            const trade = {
                ...tradeObj,
                profit: getProfit(tradeObj),
            };

            const accountStat = this.getAccountStat(accountID);

            const { rows } = accountStat;
            const prevRowIndex = rows.findIndex(t => t.reference === trade.reference);

            if (trade.is_expired && trade.is_sold && !trade.exit_tick) trade.exit_tick = '-';

            if (prevRowIndex >= 0) {
                this.setState({ [accountID]: updateRow(prevRowIndex, trade, accountStat) });
            } else {
                this.setState({ [accountID]: appendRow(trade, accountStat) });
            }
        });
    }
    rowGetter(i) {
        const { accountID } = this.props;
        const { rows } = this.state[accountID];
        return rows[rows.length - 1 - i];
    }
    export() {
        const { accountID } = this.props;

        const data = json2csv({
            data  : this.state[accountID].rows,
            fields: [
                'id',
                'timestamp',
                'reference',
                'contract_type',
                'entry_tick',
                'exit_tick',
                'sell_spot',
                'buy_price',
                'sell_price',
                'profit',
            ],
        });
        saveAs({ data, filename: 'logs.csv', type: 'text/csv;charset=utf-8' });
    }
    getAccountStat(accountID) {
        if (!(accountID in this.state)) {
            const initialInfo = this.state.initial;
            this.setState({ [accountID]: { ...initialInfo } });
            return initialInfo;
        }
        return this.state[accountID];
    }
    render() {
        const { accountID } = this.props;
        const rows = accountID in this.state ? this.state[accountID].rows : [];
        return (

                <ReactDataGrid
                    columns={this.columns}
                    rowGetter={this.rowGetter.bind(this)}
                    rowsCount={rows.length}
                    minHeight={minHeight}
                    rowHeight={rowHeight}
                />

        );
    }
}
<|endoftext|>


<commit_before>/**
 * Pimcore
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.pimcore.org/license
 *
 * @copyright  Copyright (c) 2009-2010 elements.at New Media Solutions GmbH (http://www.elements.at)
 * @license    http://www.pimcore.org/license     New BSD License
 */

pimcore.registerNS("pimcore.document.tags.textarea");
pimcore.document.tags.textarea = Class.create(pimcore.document.tag, {

    initialize: function(id, name, options, data) {
        this.id = id;
        this.name = name;
        this.setupWrapper();
        if (!options) {
            options = {};
        }
        if (!data) {
            data = "";
        }

        options.value = data;
        options.name = id + "_editable";

        if(!options.width) {
            options.width = Ext.get(id).getWidth()-2;
        }

        this.element = new Ext.form.TextArea(options);
        this.element.render(id);

        if(options["autoStyle"] !== false) {
            var styles = Ext.get(id).parent().getStyles("font-size","font-family","font-style","font-weight","font-stretch","font-variant","color","line-height","text-shadow","text-align","text-decoration","text-transform","direction","white-space","word-spacing");
            styles["background"] = "none";
            if(!options["height"]) {
                styles["height"] = "auto";
            }
            this.element.getEl().applyStyles(styles);
        }
    },

    getValue: function () {
        return this.element.getValue();
    },

    getType: function () {
        return "textarea";
    }
});<commit_msg>removed css property white-space from textarea editable (PIMCORE-1568) gets back into the code when fixing the IE8 issue (copy/paste from input)<commit_after>/**
 * Pimcore
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.pimcore.org/license
 *
 * @copyright  Copyright (c) 2009-2010 elements.at New Media Solutions GmbH (http://www.elements.at)
 * @license    http://www.pimcore.org/license     New BSD License
 */

pimcore.registerNS("pimcore.document.tags.textarea");
pimcore.document.tags.textarea = Class.create(pimcore.document.tag, {

    initialize: function(id, name, options, data) {
        this.id = id;
        this.name = name;
        this.setupWrapper();
        if (!options) {
            options = {};
        }
        if (!data) {
            data = "";
        }

        options.value = data;
        options.name = id + "_editable";

        if(!options.width) {
            options.width = Ext.get(id).getWidth()-2;
        }

        this.element = new Ext.form.TextArea(options);
        this.element.render(id);

        if(options["autoStyle"] !== false) {
            var styles = Ext.get(id).parent().getStyles("font-size","font-family","font-style","font-weight","font-stretch","font-variant","color","line-height","text-shadow","text-align","text-decoration","text-transform","direction");
            styles["background"] = "none";
            if(!options["height"]) {
                styles["height"] = "auto";
            }
            this.element.getEl().applyStyles(styles);
        }
    },

    getValue: function () {
        return this.element.getValue();
    },

    getType: function () {
        return "textarea";
    }
});<|endoftext|>