public inbox for [email protected]  
help / color / mirror / Atom feed
From: Shruti B Iyer <[email protected]>
To: Dave Page <[email protected]>
Cc: Surinder Kumar <[email protected]>
Cc: Joao Pedro De Almeida Pereira <[email protected]>
Cc: Murtuza Zabuawala <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4] [PATCH] History Tab rewrite in React
Date: Mon, 12 Jun 2017 18:22:25 +0000
Message-ID: <CACrUwhKPtLNc-J1uXE1eOZjrtRnTtqo_7YpZThNauvgw=HSwJQ@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxoyodd+KC85ZhirkhAeqGpx8iBE2niHp48oe39x-xoDrAw@mail.gmail.com>
References: <CAFS4TJYDRi6fs+wrhkj89gFe3xaFOkoApCK+AU2O4DfU2XfXog@mail.gmail.com>
	<CAKKotZQSTtJVkBKMeFi9vTrPugBEbEE38dhbbn-hr+hpd0Qzbw@mail.gmail.com>
	<CA+OCxowChMtZzaSwrG5Sganh4LiMQcgL7BpoqhTxZga30DqDXw@mail.gmail.com>
	<CAKKotZRmvbScMdUtjMgUJdRe-EXaqDoDbYBxrMZrkbVDVR_iUg@mail.gmail.com>
	<CA+OCxow3e=q9w_e=Kkw1ebZJ82EM8RyFmiTaXihdqhX6HbzZhw@mail.gmail.com>
	<CAFS4TJapVubRP_BK01GwYjfN7zpaTbCMX+CROC4mV93aL08DOg@mail.gmail.com>
	<CA+OCxozPDpP_5zdLjOSQhiGHgzE-QDsFv8-WyC8-XTxf5+uSJg@mail.gmail.com>
	<CAE+jjakzXbzvtai0Z2q0bXfecMhDzZwtn+p8mQwV7kfRGPON_Q@mail.gmail.com>
	<CA+OCxoxkM--KjFWt7FkJm+mrL5sA9jdZ59YN7=n=2ZMrkH8iSg@mail.gmail.com>
	<CAFS4TJaZA0U_BtiUBdx1jT6Z5bQzMQbjhwrjimDLgfkTb=gWJg@mail.gmail.com>
	<CAE+jjakrsf7f41LyCy6BWvjrsH9FyKK98koO0MgUkR0qmbk-uw@mail.gmail.com>
	<CA+OCxoxH+5gAjw17rWQb-jqNBeQGgOs4Q=GZ7KEwdmGHOQrsBw@mail.gmail.com>
	<CAHowoHZJtKn8F9XuiK2yFT=_OcgG7Ctx83iiFab17PwexajfDQ@mail.gmail.com>
	<CA+OCxozz7pbMQi_h-USovgwA2XkVzpeAUuK-zcro4La-_KjgtA@mail.gmail.com>
	<CAFS4TJYRuaRkHguU_BkMAa4vv7Xy_aa37B4M4HSbJ0h2QpmcFA@mail.gmail.com>
	<CA+OCxozdD8_4uyu9f+CJTjtH0x8bcKoeNMtRDcSHXSKStAPwbQ@mail.gmail.com>
	<CAE+jjakmryMDovhh+OMsw=FSTj5W62CuhbdQS4WpjcLSs9jO4g@mail.gmail.com>
	<CAM5-9D-wS-9qwMwvpUCwc8s=KumY+k8pkM7tOt1eysuFvq3n-w@mail.gmail.com>
	<CAFS4TJZtCDqoenHREPC7PEopwYVyPGUh0_9WtxQKNV2w_WLAjw@mail.gmail.com>
	<CAM5-9D9cm88KaiNjAwyNQG7+0FNxcAEJ6xaWTS9+pJ-6uLLTWQ@mail.gmail.com>
	<CAGRPzo-byoSsREPZxJ-bEbse2c2emFF5UMD=SGCJHWduk8kiCA@mail.gmail.com>
	<CA+OCxox3vW4dpK5w9LuL=axXpp_xR5V14oLbGYoe4N9zSOP3dA@mail.gmail.com>
	<CAHowoHa-xGTQgckQC431jusaHD2Eg3S0KCrn24yt9uiS0ow7bA@mail.gmail.com>
	<CA+OCxoy3gVbGiPL3hi250Ya0qMOURYOERH5XxYrnrt2Co2v-xw@mail.gmail.com>
	<CAHowoHZFkDQrMO0-e2gx3ffo3kB56Qwb3jT2S1M7Sc8Bpo0Usw@mail.gmail.com>
	<CA+OCxoxtxBO-mWfUokBu1WG8VH4E6MrHj16mrKT=L0svYvKfGg@mail.gmail.com>
	<CACrUwhJ_YXJuZGbBrMKrCTq5qQ3JJ8zrpTu_fauOC1KXcmADNg@mail.gmail.com>
	<CA+OCxowHBoAMBE9Np--JupNneYGe__=4Zwm2JMSPGD2U6=S2Lg@mail.gmail.com>
	<CACrUwhJdAefbqff7VT8E6WR=+SZH-JcomH4DbVA_nwwaO1672A@mail.gmail.com>
	<CA+OCxozwJsocmphyejzF1iMSJR7+h5bYzwT4GFDRcf7qtFQ_2g@mail.gmail.com>
	<CA+OCxoyodd+KC85ZhirkhAeqGpx8iBE2niHp48oe39x-xoDrAw@mail.gmail.com>
List-Unsubscribe:  <mailto:[email protected]?body=unsub%20pgadmin-hackers>

Hi Dave,

We regenerated the patch to add new tasks to package.json to compile react
code for development and to not minimize it. This should fix the error you
captured in the screenshot.

The new task to lint and bundle everything for development is:
yarn run bundle:dev

The new task only bundle everything is:
yarn run webpacker:dev

We also changed the task test:feature to bundle without optimization before
we start the tests.

When we ran these commands in our machine, they did not display any error.
Is it possible that you forgot to run yarn install before running the
webpacker task? We are asking this because the errors look like missing
node packages.

Thanks
Shruti & Joao

On Mon, Jun 12, 2017 at 12:15 PM Dave Page <[email protected]> wrote:

> To add to that; running the JS tests gives:
>
> ERROR in ./regression/javascript/history/query_history_entry_spec.jsx
> Module not found: Error: Can't resolve 'jasmine-enzyme' in
> '/Users/dpage/git/pgadmin4/web/regression/javascript/history'
>  @ ./regression/javascript/history/query_history_entry_spec.jsx 13:21-46
>
> ERROR in ./pgadmin/static/jsx/history/query_history_entry.jsx
> Module not found: Error: Can't resolve 'immutability-helper' in
> '/Users/dpage/git/pgadmin4/web/pgadmin/static/jsx/history'
>  @ ./pgadmin/static/jsx/history/query_history_entry.jsx 13:26-56
>  @ ./regression/javascript/history/query_history_entry_spec.jsx
>
> ERROR in ./pgadmin/static/jsx/history/query_history_entry.jsx
> Module not found: Error: Can't resolve 'moment' in
> '/Users/dpage/git/pgadmin4/web/pgadmin/static/jsx/history'
>  @ ./pgadmin/static/jsx/history/query_history_entry.jsx 17:14-31
>  @ ./regression/javascript/history/query_history_entry_spec.jsx
>
> ERROR in ./regression/javascript/history/query_history_spec.jsx
> Module not found: Error: Can't resolve 'jasmine-enzyme' in
> '/Users/dpage/git/pgadmin4/web/regression/javascript/history'
>  @ ./regression/javascript/history/query_history_spec.jsx 19:21-46
> webpack: Failed to compile.
> PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
>   Error: Cannot find module "immutability-helper"
>   at regression/javascript/history/query_history_entry_spec.jsx:30705
>
>
> PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
>   Error: Cannot find module "immutability-helper"
>   at regression/javascript/history/query_history_spec.jsx:30705
>
>
> error Command failed with exit code 1.
> info Visit https://yarnpkg.com/en/docs/cli/run for documentation about
> this command.
> error Command failed with exit code 1.
> info Visit https://yarnpkg.com/en/docs/cli/run for documentation about
> this command.
>
>
> Also, while I think of it, why the addition of the delay to app_starter.py?
>
>
> On Mon, Jun 12, 2017 at 5:12 PM, Dave Page <[email protected]> wrote:
> > Hi,
> >
> > So 01 and 02 are now committed :-).
> >
> > 03 has a couple of problems though (likely the same):
> >
> > - Running the webpacker results in:
> >
> > (pgadmin4)piranha:web dpage$ yarn run webpacker
> > yarn run v0.24.4
> > $ yarn run webpack -- --optimize-minimize --config webpack.config.js
> > yarn run v0.24.4
> > $ "/Users/dpage/git/pgadmin4/web/node_modules/.bin/webpack"
> > --optimize-minimize --config webpack.config.js
> > (node:19446) DeprecationWarning: loaderUtils.parseQuery() received a
> > non-string value which can be problematic, see
> > https://github.com/webpack/loader-utils/issues/56
> > parseQuery() will be replaced with getOptions() in the next major
> > version of loader-utils.
> > Hash: a5e75aa70eb6b09bdb78
> > Version: webpack 2.3.3
> > Time: 3983ms
> >              Asset     Size  Chunks             Chunk Names
> > reactComponents.js   222 kB       0  [emitted]  reactComponents
> >         history.js  1.58 kB       1  [emitted]  history
> >    [0] /Users/dpage/git/pgadmin4/web/~/process/browser.js 5.42 kB {0}
> [built]
> >   [18] /Users/dpage/git/pgadmin4/web/~/react-dom/lib/ReactReconciler.js
> > 6.21 kB {0} [built]
> >   [19] /Users/dpage/git/pgadmin4/web/~/react/lib/React.js 2.69 kB {0}
> [built]
> >   [31] /Users/dpage/git/pgadmin4/web/~/react/react.js 56 bytes {0}
> [built]
> >   [80] ./js/history/history_collection.js 1.91 kB {1} [built]
> >   [81] ./jsx/history/query_history.jsx 3.65 kB {0} [built]
> >   [82] /Users/dpage/git/pgadmin4/web/~/react-dom/index.js 59 bytes {0}
> [built]
> >   [83] ./js/history/index.js 690 bytes {1} [built]
> >   [84] ./jsx/components.jsx 599 bytes {0} [built]
> >   [85] ./jsx/history/query_history_entry.jsx 5.21 kB {0} [built]
> >  [113] /Users/dpage/git/pgadmin4/web/~/react-dom/lib/ReactDOM.js 5.14
> > kB {0} [built]
> >  [175] /Users/dpage/git/pgadmin4/web/~/react/lib/ReactDOMFactories.js
> > 5.53 kB {0} [built]
> >  [176] /Users/dpage/git/pgadmin4/web/~/react/lib/ReactPropTypes.js
> > 15.8 kB {0} [built]
> >  [177] /Users/dpage/git/pgadmin4/web/~/react/lib/ReactPureComponent.js
> > 1.32 kB {0} [built]
> >  [178] /Users/dpage/git/pgadmin4/web/~/react/lib/ReactVersion.js 350
> > bytes {0} [built]
> >     + 167 hidden modules
> >
> > ERROR in ./jsx/history/query_history_entry.jsx
> > Module not found: Error: Can't resolve 'immutability-helper' in
> > '/Users/dpage/git/pgadmin4/web/pgadmin/static/jsx/history'
> >  @ ./jsx/history/query_history_entry.jsx 13:26-56
> >  @ ./jsx/history/query_history.jsx
> >  @ ./jsx/components.jsx
> >
> > ERROR in ./jsx/history/query_history_entry.jsx
> > Module not found: Error: Can't resolve 'moment' in
> > '/Users/dpage/git/pgadmin4/web/pgadmin/static/jsx/history'
> >  @ ./jsx/history/query_history_entry.jsx 17:14-31
> >  @ ./jsx/history/query_history.jsx
> >  @ ./jsx/components.jsx
> > error Command failed with exit code 2.
> > info Visit https://yarnpkg.com/en/docs/cli/run for documentation about
> > this command.
> > error Command failed with exit code 1.
> > info Visit https://yarnpkg.com/en/docs/cli/run for documentation about
> > this command
> >
> >
> > - If I try to run pgAdmin, I get a script error in the UI, and console
> > output as attached (sorry for the screenshot, I've yet to find a good
> > way to copy/paste that info without losing the formatting).
> >
> > Thanks.
> >
> >
> > On Mon, Jun 12, 2017 at 3:53 PM, Shruti B Iyer <[email protected]> wrote:
> >> Hi Hackers,
> >>
> >> Attached are the updated patches that apply on top of master.
> >>
> >> Thanks,
> >> Shruti & Joao
> >>
> >>
> >> On Mon, Jun 12, 2017 at 10:44 AM Dave Page <[email protected]> wrote:
> >>>
> >>> Hi Shruti
> >>>
> >>> On Mon, Jun 12, 2017 at 3:24 PM, Shruti B Iyer <[email protected]>
> wrote:
> >>> >
> >>> > Hello Dave,
> >>> >
> >>> > Thanks for making those fixes and sharing them with us. We tried
> >>> > applying
> >>> > the patch and it looks like there are some missing file changes from
> >>> > your
> >>> > patch that were present in ours, like the Make.bat file changes. But
> we
> >>> > will
> >>> > add them when we send you the new patches.
> >>>
> >>> Hmm, I wonder if I missed them because I applied the patch in a sub
> >>> directory.
> >>>
> >>> > While trying to generate the new patches we realized some tests are
> >>> > failing
> >>> > in master branch due to an internal server error:
> >>> >
> >>> > 2017-06-12 10:04:11,938: INFO werkzeug: 127.0.0.1 - - [12/Jun/2017
> >>> > 10:04:11]
> >>> > "GET /browser/table/sql/1/1/12669/2200/81920 HTTP/1.1" 500 -
> >>> > Traceback (most recent call last):
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 2000, in __call__
> >>> >     return self.wsgi_app(environ, start_response)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1991, in wsgi_app
> >>> >     response = self.make_response(self.handle_exception(e))
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1567, in handle_exception
> >>> >     reraise(exc_type, exc_value, tb)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1988, in wsgi_app
> >>> >     response = self.full_dispatch_request()
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1641, in full_dispatch_request
> >>> >     rv = self.handle_user_exception(e)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1544, in handle_user_exception
> >>> >     reraise(exc_type, exc_value, tb)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1639, in full_dispatch_request
> >>> >     rv = self.dispatch_request()
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/app.py",
> >>> > line 1625, in dispatch_request
> >>> >     return self.view_functions[rule.endpoint](**req.view_args)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/.pyenv/versions/2.7.10/envs/pgadmin/lib/python2.7/site-packages/flask/views.py",
> >>> > line 84, in view
> >>> >     return self.dispatch_request(*args, **kwargs)
> >>> >   File
> "/Users/pivotal/workspace/pgadmin4/web/pgadmin/browser/utils.py",
> >>> > line 235, in dispatch_request
> >>> >     return method(*args, **kwargs)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/workspace/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py",
> >>> > line 315, in wrap
> >>> >     return f(*args, **kwargs)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/workspace/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py",
> >>> > line 2555, in sql
> >>> >     data = self._formatter(did, scid, tid, data)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/workspace/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py",
> >>> > line 1081, in _formatter
> >>> >     data = self._columns_formatter(tid, data)
> >>> >   File
> >>> >
> >>> >
> "/Users/pivotal/workspace/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py",
> >>> > line 663, in _columns_formatter
> >>> >     column['attlen'] = matchObj.group(1)
> >>> > AttributeError: 'NoneType' object has no attribute 'group'
> >>> >
> >>> > Was this introduced in a previous patch?
> >>>
> >>> Yes, it looks like it. For some reason it's not failing on the Jenkins
> >>> server though. I'll ask Khushboo to fix it.
> >>>
> >>> > We will recreate the patches and send them ASAP.
> >>>
> >>> Thanks!
> >>>
> >>> > Thanks
> >>> > Shruti & Joao
> >>> >
> >>> > On Mon, Jun 12, 2017 at 6:59 AM Dave Page <[email protected]> wrote:
> >>> >>
> >>> >> Hi
> >>> >>
> >>> >> OK, so Ashesh and I spend much of the morning on this.
> >>> >>
> >>> >> Patch 01 - Applied.
> >>> >> Patch 02:
> >>> >>
> >>> >> - karma.conf.js wouldn't patch; I've manually handled that.
> >>> >> - test-main.js wouldn't patch. The diff looked like it was trying to
> >>> >> empty it; I have removed it instead.
> >>> >> - The imports in pgAdmin4.py need to be made after the app root is
> >>> >> added to the path.
> >>> >> - The JS bundler should be in pgadmin/utils, not pgadmin/tools
> (which
> >>> >> is intended for plugin modules)
> >>> >> - The tests were failing following some changes Ashesh pushed
> earlier
> >>> >> to add a client-side url_for function.
> >>> >> - pgAdmin4.py was attempting to run the bundler on every startup.
> I've
> >>> >> wrapped those called in "if config.DEBUG:" conditionals, as
> typically
> >>> >> an installation for an end-user will be in a read-only directory.
> >>> >>
> >>> >> We've fixed all of that in the attached patch. I'm not sure why it's
> >>> >> so much bigger than yours.
> >>> >>
> >>> >> The following issues are outstanding; please take a look at them:
> >>> >>
> >>> >> - There is no update to the Windows installer generation code
> (needed
> >>> >> in 2 places unfortunately; Make.bat and Make-MinGW.bat).
> >>> >>
> >>> >> - The updates to the other packages call "yarn run webpacker" which
> is
> >>> >> an undefined target.
> >>> >>
> >>> >> I haven't looked at patch 03 yet, but Ashesh did tell me it won't
> >>> >> apply for him. Patch 4 is also untested at this stage.
> >>> >>
> >>> >> If the issues above can be fixed, we can get patch 2 applied then
> move
> >>> >> on from there.
> >>> >>
> >>> >> I'll hold off on Harshal's patch for the Query Tool's load on demand
> >>> >> to give you a chance to get this done.
> >>> >>
> >>> >> Thanks.
> >>> >>
> >>> >> On Sat, Jun 10, 2017 at 2:52 AM, George Gelashvili
> >>> >> <[email protected]> wrote:
> >>> >> > Hi Dave,
> >>> >> >
> >>> >> > Our patch touches code also changed by the patches that were
> recently
> >>> >> > committed.
> >>> >> > That's likely what's causing this issue. We've rebased on top of
> the
> >>> >> > new
> >>> >> > state of master.
> >>> >> >
> >>> >> > We had initially kept the yarn.lock .gitignored, but ran into an
> >>> >> > issue
> >>> >> > rather early on where an upgraded dependency introduced a
> regression.
> >>> >> > Checking-in the yarn.lock provides the "know your dependency
> version"
> >>> >> > benefit of vendorizing code without vendorization's drawback of
> >>> >> > having
> >>> >> > to
> >>> >> > manually manage your dependencies.
> >>> >> >
> >>> >> > It is safe to delete a yarn.lock before applying a patch, as you
> are
> >>> >> > authoring master. It provides a history of the versions of each
> >>> >> > dependency
> >>> >> > that were working at the point in time of the commit. By contrast,
> >>> >> > package.json provides approximate versions and leaves room for
> >>> >> > fixes/improvements by the dependency authors to be pulled in as
> they
> >>> >> > become
> >>> >> > available.
> >>> >> >
> >>> >> > To run linter and tests:
> >>> >> >
> >>> >> > The tasks that Grunt used to manage are now defined as a set of
> >>> >> > scripts
> >>> >> > in
> >>> >> > the package.json
> >>> >> > After applying the patches--which may require deleting yarn.lock
> for
> >>> >> > the
> >>> >> > first patch--you should cd web && yarn install
> >>> >> >
> >>> >> > Then yarn test will run the linter, jasmine specs, and python
> tests
> >>> >> > including feature tests, in that order, exiting early if there are
> >>> >> > failures/errors.
> >>> >> > At the moment, the CheckForViewData test is failing on master as
> well
> >>> >> > as
> >>> >> > in
> >>> >> > each of these patches; that should be resolved as RM2477.
> >>> >> >
> >>> >> > Thanks!
> >>> >> > George and Matt
> >>> >> >
> >>> >> >
> >>> >> > On Thu, Jun 8, 2017 at 9:15 AM, Dave Page <[email protected]>
> wrote:
> >>> >> >>
> >>> >> >> Hi George
> >>> >> >>
> >>> >> >> On Wed, Jun 7, 2017 at 10:21 PM, George Gelashvili
> >>> >> >> <[email protected]> wrote:
> >>> >> >> > Hi Dave,
> >>> >> >> >
> >>> >> >> > I split the linting out into an intermediate commit, and
> rebased
> >>> >> >> > on
> >>> >> >> > top
> >>> >> >> > of
> >>> >> >> > master.
> >>> >> >>
> >>> >> >> Unfortunately, it still doesn't apply:
> >>> >> >>
> >>> >> >> error: patch failed: web/regression/javascript/test-main.js:1
> >>> >> >> error: removal patch leaves file contents
> >>> >> >> error: web/regression/javascript/test-main.js: patch does not
> apply
> >>> >> >> Checking patch web/regression/requirements.txt...
> >>> >> >> Checking patch web/webpack.config.js...
> >>> >> >> Checking patch web/webpack.test.config.js...
> >>> >> >> Checking patch web/yarn.lock...
> >>> >> >> error: web/yarn.lock: already exists in working directory
> >>> >> >> Applied patch .gitignore cleanly.
> >>> >> >> Applied patch Make.bat cleanly.
> >>> >> >> Applied patch README cleanly.
> >>> >> >> Applied patch pkg/mac/build.sh cleanly.
> >>> >> >> Applied patch pkg/pip/build.sh cleanly.
> >>> >> >> Applied patch pkg/src/build.sh cleanly.
> >>> >> >> Applied patch web/.eslintrc.js cleanly.
> >>> >> >> Applied patch web/karma.conf.js cleanly.
> >>> >> >> Applied patch web/package.json cleanly.
> >>> >> >> Applied patch web/pgAdmin4.py cleanly.
> >>> >> >> Applied patch web/pgadmin/static/jsx/components.jsx cleanly.
> >>> >> >> Applied patch web/pgadmin/tools/javascript/__init__.py cleanly.
> >>> >> >> Applied patch web/pgadmin/tools/javascript/javascript_bundler.py
> >>> >> >> cleanly.
> >>> >> >> Applied patch web/pgadmin/tools/javascript/tests/__init__.py
> >>> >> >> cleanly.
> >>> >> >> Applied patch
> >>> >> >> web/pgadmin/tools/javascript/tests/test_javascript_bundler.py
> >>> >> >> cleanly.
> >>> >> >> Applied patch web/regression/README cleanly.
> >>> >> >> Applied patch
> >>> >> >> web/regression/javascript/jasmine_capture_warnings_beforeall.js
> >>> >> >> cleanly.
> >>> >> >> Applied patch web/regression/requirements.txt cleanly.
> >>> >> >> Applied patch web/webpack.config.js cleanly.
> >>> >> >> Applied patch web/webpack.test.config.js cleanly.
> >>> >> >>
> >>> >> >> The second (lint update) patch is even worse, with significant
> >>> >> >> number
> >>> >> >> change that just don't want to apply.
> >>> >> >>
> >>> >> >> Clearly yarn.lock needs to be removed from there repo.
> >>> >> >>
> >>> >> >> Once I can apply a version of this, how should I be running the
> >>> >> >> linter
> >>> >> >> and the unit tests?
> >>> >> >>
> >>> >> >> --
> >>> >> >> Dave Page
> >>> >> >> Blog: http://pgsnake.blogspot.com
> >>> >> >> Twitter: @pgsnake
> >>> >> >>
> >>> >> >> EnterpriseDB UK: http://www.enterprisedb.com
> >>> >> >> The Enterprise PostgreSQL Company
> >>> >> >
> >>> >> >
> >>> >>
> >>> >>
> >>> >>
> >>> >> --
> >>> >> Dave Page
> >>> >> Blog: http://pgsnake.blogspot.com
> >>> >> Twitter: @pgsnake
> >>> >>
> >>> >> EnterpriseDB UK: http://www.enterprisedb.com
> >>> >> The Enterprise PostgreSQL Company
> >>> >>
> >>> >> --
> >>> >> Sent via pgadmin-hackers mailing list (
> [email protected])
> >>> >> To make changes to your subscription:
> >>> >> http://www.postgresql.org/mailpref/pgadmin-hackers
> >>>
> >>>
> >>>
> >>> --
> >>> Dave Page
> >>> Blog: http://pgsnake.blogspot.com
> >>> Twitter: @pgsnake
> >>>
> >>> EnterpriseDB UK: http://www.enterprisedb.com
> >>> The Enterprise PostgreSQL Company
> >
> >
> >
> > --
> > Dave Page
> > Blog: http://pgsnake.blogspot.com
> > Twitter: @pgsnake
> >
> > EnterpriseDB UK: http://www.enterprisedb.com
> > The Enterprise PostgreSQL Company
>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] 03-create-query-history-list.patch (46.3K, 3-03-create-query-history-list.patch)
  download | inline diff:
diff --git a/web/.eslintrc.js b/web/.eslintrc.js
index c60569db..98486dcd 100644
--- a/web/.eslintrc.js
+++ b/web/.eslintrc.js
@@ -5,7 +5,10 @@ module.exports = {
     'amd': true,
     'jasmine': true,
   },
-  'extends': 'eslint:recommended',
+  'extends': [
+    'eslint:recommended',
+    "plugin:react/recommended",
+  ],
   'parserOptions': {
     'ecmaFeatures': {
       'experimentalObjectRestSpread': true,
@@ -40,6 +43,6 @@ module.exports = {
     'comma-dangle': [
       'error',
       'always-multiline'
-    ]
+    ],
   }
 };
\ No newline at end of file
diff --git a/web/karma.conf.js b/web/karma.conf.js
index ca988a0b..713b9f05 100644
--- a/web/karma.conf.js
+++ b/web/karma.conf.js
@@ -29,7 +29,7 @@ module.exports = function (config) {
     // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
     preprocessors: {
       'regression/javascript/**/*.js': ['webpack'],
-      // 'regression/javascript/**/*.jsx': ['webpack'],
+      'regression/javascript/**/*.jsx': ['webpack'],
     },
 
     webpack: webpackConfig,
diff --git a/web/package.json b/web/package.json
index b5d2e0cb..cd8992f9 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,9 +5,11 @@
     "babel-preset-es2015": "~6.24.0",
     "babel-preset-react": "~6.23.0",
     "enzyme": "~2.8.2",
+    "enzyme-matchers": "^3.1.0",
     "eslint": "^3.19.0",
     "eslint-plugin-react": "^6.10.3",
     "jasmine-core": "~2.5.2",
+    "jasmine-enzyme": "^3.1.0",
     "karma": "~1.5.0",
     "karma-babel-preprocessor": "^6.0.1",
     "karma-browserify": "~5.1.1",
@@ -28,7 +30,9 @@
     "babelify": "~7.3.0",
     "browserify": "~14.1.0",
     "exports-loader": "~0.6.4",
+    "immutability-helper": "^2.2.0",
     "imports-loader": "git+https://github.com/webpack-contrib/imports-loader.git#44d6f48463b256a17c1ba6fd9b5cc1449b4e379d",
+    "moment": "^2.18.1",
     "react": "~15.4.2",
     "react-dom": "~15.4.2",
     "requirejs": "~2.3.3",
@@ -38,7 +42,9 @@
   "scripts": {
     "linter": "yarn run eslint pgadmin/static/jsx/**/*.jsx pgadmin/static/js/selection/*.js regression/javascript/**/*.jsx regression/javascript/**/*.js *.js",
     "webpacker": "yarn run webpack -- --optimize-minimize --config webpack.config.js",
+    "webpacker:dev": "yarn run webpack -- --config webpack.config.js",
     "bundle": "yarn run linter && yarn run webpacker",
+    "bundle:dev": "yarn run linter && yarn run webpacker:dev",
     "test:karma-once": "yarn run linter && yarn run karma start -- --single-run",
     "test:karma": "yarn run linter && yarn run karma start",
     "test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests",
diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/pgadmin/feature_tests/query_tool_journey_test.py
new file mode 100644
index 00000000..0766193e
--- /dev/null
+++ b/web/pgadmin/feature_tests/query_tool_journey_test.py
@@ -0,0 +1,111 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import pyperclip
+import time
+
+from selenium.webdriver import ActionChains
+
+from regression.python_test_utils import test_utils
+from regression.feature_utils.base_feature_test import BaseFeatureTest
+
+
+class QueryToolJourneyTest(BaseFeatureTest):
+    """
+    Tests the path through the query tool
+    """
+
+    scenarios = [
+        ("Tests the path through the query tool", dict())
+    ]
+
+    def before(self):
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
+        test_utils.create_database(self.server, "acceptance_test_db")
+        test_utils.create_table(self.server, "acceptance_test_db", "test_table")
+        self.page.add_server(self.server)
+
+    def runTest(self):
+        self._navigate_to_query_tool()
+        self._execute_query("SELECT * FROM test_table ORDER BY value")
+
+        self._test_copies_rows()
+        self._test_copies_columns()
+        self._test_history_tab()
+
+    def _test_copies_rows(self):
+        pyperclip.copy("old clipboard contents")
+        time.sleep(5)
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe"))
+        self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]").click()
+        self.page.find_by_xpath("//*[@id='btn-copy-row']").click()
+
+        self.assertEqual("'Some-Name','6','some info'",
+                         pyperclip.paste())
+
+    def _test_copies_columns(self):
+        pyperclip.copy("old clipboard contents")
+
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe"))
+        self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click()
+        self.page.find_by_xpath("//*[@id='btn-copy-row']").click()
+
+        self.assertTrue("'Some-Name'" in pyperclip.paste())
+        self.assertTrue("'Some-Other-Name'" in pyperclip.paste())
+        self.assertTrue("'Yet-Another-Name'" in pyperclip.paste())
+
+    def _test_history_tab(self):
+        self.__clear_query_tool()
+
+        editor_input = self.page.find_by_id("output-panel")
+        self.page.click_element(editor_input)
+        self._execute_query("SELECT * FROM shoes")
+
+        self.page.click_tab("History")
+        history_element = self.page.find_by_id("history_grid")
+        self.assertIn("SELECT * FROM test_table", history_element.text)
+        self.assertIn("SELECT * FROM shoes", history_element.text)
+
+    def __clear_query_tool(self):
+        self.page.click_element(self.page.find_by_xpath("//*[@id='btn-edit']"))
+        self.page.click_modal('Yes')
+
+    def _navigate_to_query_tool(self):
+        self.page.toggle_open_tree_item(self.server['name'])
+        self.page.toggle_open_tree_item('Databases')
+        self.page.toggle_open_tree_item('acceptance_test_db')
+        time.sleep(5)
+        self.page.find_by_partial_link_text("Tools").click()
+        self.page.find_by_partial_link_text("Query Tool").click()
+        self.page.click_tab('Query-1')
+        time.sleep(5)
+
+    def _execute_query(self, query):
+        ActionChains(self.page.driver).send_keys(query).perform()
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe"))
+        self.page.find_by_id("btn-flash").click()
+
+    def after(self):
+        self.page.close_query_tool()
+        self.page.remove_server(self.server)
+
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
diff --git a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py
index 959b2c19..094dfed6 100644
--- a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py
+++ b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py
@@ -83,7 +83,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
 
         # If debugger plugin is not found
         if is_error and is_error == "Debugger Error":
-            self.page.click_modal_ok()
+            self.page.click_modal('OK')
             self.skipTest("Please make sure that debugger plugin is properly configured")
         else:
             time.sleep(2)
diff --git a/web/pgadmin/static/js/history/history_collection.js b/web/pgadmin/static/js/history/history_collection.js
new file mode 100644
index 00000000..f7b6acd7
--- /dev/null
+++ b/web/pgadmin/static/js/history/history_collection.js
@@ -0,0 +1,34 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export default class HistoryCollection {
+
+  constructor(history_model) {
+    this.historyList = history_model;
+    this.onChange(() => {});
+  }
+
+  length() {
+    return this.historyList.length;
+  }
+
+  add(object) {
+    this.historyList.push(object);
+    this.onChangeHandler(this.historyList);
+  }
+
+  reset() {
+    this.historyList = [];
+    this.onChangeHandler(this.historyList);
+  }
+
+  onChange(onChangeHandler) {
+    this.onChangeHandler = onChangeHandler;
+  }
+}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/history/index.js b/web/pgadmin/static/js/history/index.js
new file mode 100644
index 00000000..834878a8
--- /dev/null
+++ b/web/pgadmin/static/js/history/index.js
@@ -0,0 +1,14 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import historyCollection from './history_collection';
+
+export {
+  historyCollection,
+};
diff --git a/web/pgadmin/static/jsx/components.jsx b/web/pgadmin/static/jsx/components.jsx
index 5bcb5208..6ff34e4c 100644
--- a/web/pgadmin/static/jsx/components.jsx
+++ b/web/pgadmin/static/jsx/components.jsx
@@ -1,8 +1,10 @@
 
 import React from 'react';
 import {render} from 'react-dom';
+import QueryHistory from './history/query_history';
 
 export {
   render,
   React,
+  QueryHistory,
 };
\ No newline at end of file
diff --git a/web/pgadmin/static/jsx/history/query_history.jsx b/web/pgadmin/static/jsx/history/query_history.jsx
new file mode 100644
index 00000000..d36f5ce9
--- /dev/null
+++ b/web/pgadmin/static/jsx/history/query_history.jsx
@@ -0,0 +1,49 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import QueryHistoryEntry from './query_history_entry';
+
+const liStyle = {
+  borderBottom: '1px solid #cccccc',
+};
+
+export default class QueryHistory extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      history: [],
+    };
+  }
+
+  componentWillMount() {
+    this.setState({history: this.props.historyCollection.historyList});
+    this.props.historyCollection.onChange((historyList) => this.setState({history: historyList}));
+  }
+
+  render() {
+    return <ul>
+      {_.chain(this.state.history)
+        .sortBy(historyEntry => historyEntry.start_time)
+        .reverse()
+        .map((entry, index) =>
+        <li key={index} style={liStyle}>
+          <QueryHistoryEntry historyEntry={entry}/>
+        </li>)
+        .value()
+      }
+    </ul>;
+  }
+}
+
+QueryHistory.propTypes = {
+  historyCollection: React.PropTypes.object.isRequired,
+};
\ No newline at end of file
diff --git a/web/pgadmin/static/jsx/history/query_history_entry.jsx b/web/pgadmin/static/jsx/history/query_history_entry.jsx
new file mode 100644
index 00000000..d66cb3a7
--- /dev/null
+++ b/web/pgadmin/static/jsx/history/query_history_entry.jsx
@@ -0,0 +1,93 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import update from 'immutability-helper';
+import moment from 'moment';
+
+const outerDivStyle = {
+  paddingLeft: '10px',
+  fontFamily: 'monospace',
+  paddingRight: '20px',
+  fontSize: '14px',
+  backgroundColor: '#FFF',
+};
+const sqlStyle = {
+  textOverflow: 'ellipsis',
+  overflow: 'hidden',
+  whiteSpace: 'nowrap',
+  userSelect: 'auto',
+};
+const secondLineStyle = {
+  display: 'flex',
+  flexDirection: 'row',
+  justifyContent: 'space-between',
+  fontSize: '13px',
+  color: '#888888',
+};
+const timestampStyle = {
+  alignSelf: 'flex-start',
+};
+const rowsAffectedStyle = {
+  alignSelf: 'flex-end',
+};
+const errorMessageStyle = {
+  textOverflow: 'ellipsis',
+  overflow: 'hidden',
+  whiteSpace: 'nowrap',
+  userSelect: 'auto',
+  fontSize: '13px',
+  color: '#888888',
+};
+
+export default class QueryHistoryEntry extends React.Component {
+  formatDate(date) {
+    return (moment(date).format('MMM D YYYY [–] HH:mm:ss'));
+  }
+
+  render() {
+    return (
+      <div style={this.queryEntryBackgroundColor()}>
+        <div style={sqlStyle}>
+          {this.props.historyEntry.query}
+        </div>
+        <div style={secondLineStyle}>
+          <div style={timestampStyle}>
+            {this.formatDate(this.props.historyEntry.start_time)} /
+            total time: {this.props.historyEntry.total_time}
+          </div>
+          <div style={rowsAffectedStyle}>
+            {this.props.historyEntry.row_affected} rows affected
+          </div>
+        </div>
+        <div style={errorMessageStyle}>
+          {this.props.historyEntry.message}
+        </div>
+      </div>
+    );
+  }
+
+  queryEntryBackgroundColor() {
+    if (!this.props.historyEntry.status) {
+      return update(outerDivStyle, {$merge: {backgroundColor: '#F7D0D5'}});
+    }
+    return outerDivStyle;
+  }
+}
+
+QueryHistoryEntry.propTypes = {
+  historyEntry: React.PropTypes.shape({
+    query: React.PropTypes.string,
+    start_time: React.PropTypes.instanceOf(Date),
+    status: React.PropTypes.bool,
+    total_time: React.PropTypes.string,
+    row_affected: React.PropTypes.int,
+    message: React.PropTypes.string,
+  }),
+};
\ No newline at end of file
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index ea8fedbf..de857415 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -9,7 +9,10 @@ define([
     'sources/slickgrid/event_handlers/handle_query_output_keyboard_event',
     'sources/selection/xcell_selection_model',
     'sources/selection/set_staged_rows',
-    'sources/gettext', 'sources/sqleditor_utils',
+    'sources/gettext',
+    'sources/sqleditor_utils',
+    'sources/generated/history',
+    'sources/generated/reactComponents',
 
     'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
     'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
@@ -32,9 +35,9 @@ define([
     'slickgrid/plugins/slick.rowselectionmodel',
     'slickgrid/slick.grid'
 ], function(
-    $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector,
-    ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
-    XCellSelectionModel, setStagedRows, gettext, SqlEditorUtils
+  $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror,
+  pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
+    XCellSelectionModel, setStagedRows, gettext, SqlEditorUtils, HistoryBundle, reactComponents
 ) {
     /* Return back, this has been called more than once */
     if (pgAdmin.SqlEditor)
@@ -874,147 +877,14 @@ define([
 
         // Remove any existing grid first
         if (self.history_grid) {
-            self.history_grid.remove();
+          self.history_grid.remove();
         }
 
-        var history_model = Backbone.Model.extend({
-          defaults: {
-            status: undefined,
-            start_time: undefined,
-            query: undefined,
-            row_affected: 0,
-            row_retrieved: 0,
-            total_time: undefined,
-            message: ''
-          }
-        });
-
-        var history_collection = self.history_collection = new (Backbone.Collection.extend({
-            model: history_model,
-            // comparator to sort the history in reverse order of the start_time
-            comparator: function(a, b) {
-              return -a.get('start_time').localeCompare(b.get('start_time'));
-            }
-        }));
-        var columns = [{
-            name: "status",
-            label: "",
-            cell: Backgrid.Cell.extend({
-              class: 'sql-status-cell',
-              render: function() {
-                this.$el.empty();
-                var $btn = $('<button></button>', {
-                  class: 'btn btn-circle'
-                }).appendTo(this.$el);
-                var $circleDiv = $('<i></i>', {class: 'fa'}).appendTo($btn);
-                if (this.model.get('status')) {
-                  $btn.addClass('btn-success');
-                  $circleDiv.addClass('fa-check');
-                } else {
-                  $btn.addClass('btn-danger');
-                  $circleDiv.addClass('fa-times');
-                }
-
-                return this;
-              },
-              editable: false
-            }),
-            editable: false
-          }, {
-            name: "start_time",
-            label: "Date",
-            cell: "string",
-            editable: false,
-            resizeable: true
-          }, {
-            name: "query",
-            label: "Query",
-            cell: "string",
-            editable: false,
-            resizeable: true
-          }, {
-            name: "row_affected",
-            label: "Rows affected",
-            cell: "integer",
-            editable: false,
-            resizeable: true
-          }, {
-            name: "total_time",
-            label: "Total Time",
-            cell: "string",
-            editable: false,
-            resizeable: true
-          }, {
-            name: "message",
-            label: "Message",
-            cell: "string",
-            editable: false,
-            resizeable: true
-        }];
-
-
-        // Create Collection of Backgrid columns
-        var columnsColl = new Backgrid.Columns(columns);
-        var $history_grid = self.$el.find('#history_grid');
-
-        var grid = self.history_grid = new Backgrid.Grid({
-            columns: columnsColl,
-            collection: history_collection,
-            className: "backgrid table-bordered presentation table backgrid-striped"
-        });
-
-        // Render the grid
-        $history_grid.append(grid.render().$el);
-
-        var sizeAbleCol = new Backgrid.Extension.SizeAbleColumns({
-          collection: history_collection,
-          columns: columnsColl,
-          grid: self.history_grid
-        });
-
-        $history_grid.find('thead').before(sizeAbleCol.render().el);
+        self.history_collection = new HistoryBundle.historyCollection([]);
 
-        // Add resize handlers
-        var sizeHandler = new Backgrid.Extension.SizeAbleColumnsHandlers({
-          sizeAbleColumns: sizeAbleCol,
-          grid: self.history_grid,
-          saveColumnWidth: true
-        });
-
-        // sizeHandler should render only when table grid loaded completely.
-        setTimeout(function() {
-          $history_grid.find('thead').before(sizeHandler.render().el);
-        }, 1000);
-
-        // re render sizeHandler whenever history panel tab becomes visible
-        self.history_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function(ev) {
-          $history_grid.find('thead').before(sizeHandler.render().el);
-        });
-
-        // Initialized table width 0 still not calculated
-        var table_width = 0;
-        // Listen to resize events
-        columnsColl.on('resize',
-          function(columnModel, newWidth, oldWidth, offset) {
-            var $grid_el = $history_grid.find('table'),
-                tbl_orig_width = $grid_el.width(),
-                offset = oldWidth - newWidth,
-                tbl_new_width = tbl_orig_width - offset;
-
-            if (table_width == 0) {
-              table_width = tbl_orig_width
-            }
-            // Table new width cannot be less than original width
-            if (tbl_new_width >= table_width) {
-              $($grid_el).css('width', tbl_new_width + 'px');
-            }
-            else {
-              // reset if calculated tbl_new_width is less than original
-              // table width
-              tbl_new_width = table_width;
-              $($grid_el).css('width', tbl_new_width + 'px');
-            }
-        });
+        let queryHistoryElement = reactComponents.React.createElement(
+          reactComponents.QueryHistory, {historyCollection: self.history_collection});
+        reactComponents.render(queryHistoryElement, $('#history_grid')[0]);
       },
 
       // Callback function for Add New Row button click.
@@ -1317,7 +1187,7 @@ define([
         this._stopEventPropogation(ev);
         this._closeDropDown(ev);
         // ask for confirmation only if anything to clear
-        if(!self.history_collection.length) { return; }
+        if(!self.history_collection.length()) { return; }
 
         alertify.confirm(gettext("Clear history"),
           gettext("Are you sure you wish to clear the history?"),
@@ -2140,11 +2010,13 @@ define([
             $("#btn-flash").prop('disabled', false);
             self.trigger('pgadmin-sqleditor:loading-icon:hide');
             self.gridView.history_collection.add({
-              'status' : status, 'start_time': self.query_start_time.toString(),
-              'query': self.query, 'row_affected': self.rows_affected,
-              'total_time': self.total_time, 'message':msg
+              'status' : status,
+              'start_time': self.query_start_time,
+              'query': self.query,
+              'row_affected': self.rows_affected,
+              'total_time': self.total_time,
+              'message':msg,
             });
-            self.gridView.history_collection.sort();
           }
         },
 
@@ -2417,10 +2289,13 @@ define([
 
                 // Update the sql results in history tab
                 _.each(res.data.query_result, function(r) {
-                  self.gridView.history_collection.add(
-                    {'status' : r.status, 'start_time': self.query_start_time.toString(),
-                    'query': r.sql, 'row_affected': r.rows_affected,
-                    'total_time': self.total_time, 'message': r.result
+                  self.gridView.history_collection.add({
+                    'status': r.status,
+                    'start_time': self.query_start_time,
+                    'query': r.sql,
+                    'row_affected': r.rows_affected,
+                    'total_time': self.total_time,
+                    'message': r.result,
                   });
                 });
                 self.trigger('pgadmin-sqleditor:loading-icon:hide');
@@ -3366,7 +3241,7 @@ define([
 
                 var msg = e.responseText;
                 if (e.responseJSON != undefined &&
-                  e.responseJSON.errormsg != undefined)
+                    e.responseJSON.errormsg != undefined)
                   msg = e.responseJSON.errormsg;
 
                 alertify.alert('Get Object Name Error', msg);
diff --git a/web/pgadmin/utils/javascript/javascript_bundler.py b/web/pgadmin/utils/javascript/javascript_bundler.py
index 4ca2da67..6016adb0 100644
--- a/web/pgadmin/utils/javascript/javascript_bundler.py
+++ b/web/pgadmin/utils/javascript/javascript_bundler.py
@@ -58,5 +58,5 @@ def webdir_path():
 
 def try_building_js():
     with pushd(webdir_path()):
-        if call(['yarn', 'run', 'bundle']) != 0:
+        if call(['yarn', 'run', 'bundle:dev']) != 0:
             raise OSError('Error executing bundling the application')
diff --git a/web/pgadmin/utils/javascript/tests/test_javascript_bundler.py b/web/pgadmin/utils/javascript/tests/test_javascript_bundler.py
index 6701138d..c4ebad0f 100644
--- a/web/pgadmin/utils/javascript/tests/test_javascript_bundler.py
+++ b/web/pgadmin/utils/javascript/tests/test_javascript_bundler.py
@@ -61,7 +61,7 @@ class JavascriptBundlerTestCase(BaseTestGenerator):
         self.mockOs.listdir.return_value = [u'history.js', u'reactComponents.js']
 
         javascriptBundler.bundle()
-        self.mockSubprocess.call.assert_called_once_with(['yarn', 'run', 'bundle'])
+        self.mockSubprocess.call.assert_called_once_with(['yarn', 'run', 'bundle:dev'])
 
         reportedState = javascriptBundler.report()
         expectedState = self.JsState.NEW
@@ -110,7 +110,7 @@ class JavascriptBundlerTestCase(BaseTestGenerator):
         self.mockOs.listdir.return_value = [u'history.js', u'reactComponents.js']
 
         javascriptBundler.bundle()
-        self.mockSubprocess.call.assert_called_once_with(['yarn', 'run', 'bundle'])
+        self.mockSubprocess.call.assert_called_once_with(['yarn', 'run', 'bundle:dev'])
 
         reportedState = javascriptBundler.report()
         expectedState = self.JsState.OLD
diff --git a/web/regression/feature_utils/app_starter.py b/web/regression/feature_utils/app_starter.py
index 96cc516b..f40d6921 100644
--- a/web/regression/feature_utils/app_starter.py
+++ b/web/regression/feature_utils/app_starter.py
@@ -11,6 +11,7 @@ import subprocess
 import signal
 import random
 
+import time
 
 class AppStarter:
     """ Helper for starting the full pgadmin4 app and loading the page via
@@ -40,6 +41,7 @@ class AppStarter:
         )
 
         self.driver.set_window_size(1024, 1024)
+        time.sleep(10)
         self.driver.get(
             "http://" + self.app_config.DEFAULT_SERVER + ":" +
             random_server_port)
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index 46d50156..a1cb7147 100644
--- a/web/regression/feature_utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -33,15 +33,16 @@ class PgadminPage:
     def reset_layout(self):
         self.click_element(self.find_by_partial_link_text("File"))
         self.find_by_partial_link_text("Reset Layout").click()
-        self.click_modal_ok()
+        self.click_modal('OK')
         self.wait_for_reloading_indicator_to_disappear()
 
-    def click_modal_ok(self):
+    def click_modal(self, button_text):
         time.sleep(0.5)
         # Find active alertify dialog in case of multiple alertify dialog & click on that dialog
-        self.click_element(
-            self.find_by_xpath("//div[contains(@class, 'alertify') and not(contains(@class, 'ajs-hidden'))]//button[.='OK']")
-        )
+        modal_button = self.find_by_xpath(
+            "//div[contains(@class, 'alertify') and not(contains(@class, 'ajs-hidden'))]//button[.='%s']"
+            % button_text)
+        self.click_element(modal_button)
 
     def add_server(self, server_config):
         self.find_by_xpath("//*[@class='aciTreeText' and contains(.,'Servers')]").click()
@@ -78,10 +79,13 @@ class PgadminPage:
 
     def remove_server(self, server_config):
         self.driver.switch_to.default_content()
-        self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']").click()
-        self.find_by_partial_link_text("Object").click()
-        self.find_by_partial_link_text("Delete/Drop").click()
-        self.click_modal_ok()
+        server_to_remove = self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']")
+        self.click_element(server_to_remove)
+        object_menu_item = self.find_by_partial_link_text("Object")
+        self.click_element(object_menu_item)
+        delete_menu_item = self.find_by_partial_link_text("Delete/Drop")
+        self.click_element(delete_menu_item)
+        self.click_modal('OK')
 
     def select_tree_item(self, tree_item_text):
         self.find_by_xpath("//*[@id='tree']//*[.='" + tree_item_text + "' and @class='aciTreeItem']").click()
@@ -130,6 +134,7 @@ class PgadminPage:
         )
 
     def click_element(self, element):
+        # driver must be here to adhere to the method contract in selenium.webdriver.support.wait.WebDriverWait.until()
         def click_succeeded(driver):
             try:
                 element.click()
@@ -175,8 +180,9 @@ class PgadminPage:
         time.sleep(sleep_time)
 
     def click_tab(self, tab_name):
-        self.find_by_xpath("//*[contains(@class,'wcTabTop')]//*[contains(@class,'wcPanelTab') "
-                           "and contains(.,'" + tab_name + "')]").click()
+        tab = self.find_by_xpath("//*[contains(@class,'wcTabTop')]//*[contains(@class,'wcPanelTab') "
+                           "and contains(.,'" + tab_name + "')]")
+        self.click_element(tab)
 
     def wait_for_input_field_content(self, field_name, content):
         def input_field_has_content(driver):
diff --git a/web/regression/javascript/check_node_visiblity_spec.js b/web/regression/javascript/check_node_visibility_spec.js
similarity index 100%
rename from web/regression/javascript/check_node_visiblity_spec.js
rename to web/regression/javascript/check_node_visibility_spec.js
diff --git a/web/regression/javascript/history/history_collection_spec.js b/web/regression/javascript/history/history_collection_spec.js
new file mode 100644
index 00000000..e1baa554
--- /dev/null
+++ b/web/regression/javascript/history/history_collection_spec.js
@@ -0,0 +1,83 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import HistoryCollection from '../../../pgadmin/static/js/history/history_collection';
+
+describe('historyCollection', function () {
+  let historyCollection, historyModel, onChangeSpy;
+  beforeEach(() => {
+    historyModel = [{some: 'thing', someOther: ['array element']}];
+    historyCollection = new HistoryCollection(historyModel);
+    onChangeSpy = jasmine.createSpy('onChangeHandler');
+
+    historyCollection.onChange(onChangeSpy);
+  });
+
+  describe('length', function () {
+    it('returns 0 when underlying history model has no elements', function () {
+      historyCollection = new HistoryCollection([]);
+
+      expect(historyCollection.length()).toBe(0);
+    });
+
+    it('returns the length of the underlying history model', function () {
+      expect(historyCollection.length()).toBe(1);
+    });
+  });
+
+  describe('add', function () {
+    let expectedHistory;
+    beforeEach(() => {
+      historyCollection.add({some: 'new thing', someOther: ['value1', 'value2']});
+
+      expectedHistory = [
+        {some: 'thing', someOther: ['array element']},
+        {some: 'new thing', someOther: ['value1', 'value2']},
+      ];
+    });
+
+    it('adds a passed entry', function () {
+      expect(historyCollection.historyList).toEqual(expectedHistory);
+    });
+
+    it('calls the onChange function', function () {
+      expect(onChangeSpy).toHaveBeenCalledWith(expectedHistory);
+    });
+  });
+
+  describe('reset', function () {
+    beforeEach(() => {
+      historyCollection.reset();
+    });
+
+    it('drops the history', function () {
+      expect(historyCollection.historyList).toEqual([]);
+      expect(historyCollection.length()).toBe(0);
+    });
+
+    it('calls the onChange function', function () {
+      expect(onChangeSpy).toHaveBeenCalledWith([]);
+    });
+  });
+
+  describe('sort', function () {
+    it('doesn\'t sort');
+  });
+
+  describe('when instantiated', function () {
+    describe('from a history model', function () {
+      it('has the historyModel', () => {
+        let content = historyCollection.historyList;
+
+        expect(content).toEqual(historyModel);
+      });
+
+    });
+  });
+});
\ No newline at end of file
diff --git a/web/regression/javascript/history/query_history_entry_spec.jsx b/web/regression/javascript/history/query_history_entry_spec.jsx
new file mode 100644
index 00000000..c86a1cfc
--- /dev/null
+++ b/web/regression/javascript/history/query_history_entry_spec.jsx
@@ -0,0 +1,50 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+
+import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
+
+import {mount} from 'enzyme';
+import jasmineEnzyme from 'jasmine-enzyme';
+
+describe('QueryHistoryEntry', () => {
+  let historyWrapper;
+  beforeEach(() => {
+    jasmineEnzyme();
+  });
+
+  describe('for a failed query', () => {
+    beforeEach(() => {
+      const historyEntry = {
+        query: 'second sql statement',
+        start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
+        status: false,
+      };
+      historyWrapper = mount(<QueryHistoryEntry historyEntry={historyEntry}/>);
+    });
+    it('displays a pink background color', () => {
+      expect(historyWrapper.find('div').first()).toHaveStyle('backgroundColor', '#F7D0D5');
+    });
+  });
+
+  describe('for a successful query', () => {
+    beforeEach(() => {
+      const historyEntry = {
+        query: 'second sql statement',
+        start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
+        status: true,
+      };
+      historyWrapper = mount(<QueryHistoryEntry historyEntry={historyEntry}/>);
+    });
+    it('does not display a pink background color', () => {
+      expect(historyWrapper.find('div').first()).toHaveStyle('backgroundColor', '#FFF');
+    });
+  });
+});
diff --git a/web/regression/javascript/history/query_history_spec.jsx b/web/regression/javascript/history/query_history_spec.jsx
new file mode 100644
index 00000000..e36988a8
--- /dev/null
+++ b/web/regression/javascript/history/query_history_spec.jsx
@@ -0,0 +1,103 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import QueryHistory from '../../../pgadmin/static/jsx/history/query_history';
+import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
+import HistoryCollection from '../../../pgadmin/static/js/history/history_collection';
+import jasmineEnzyme from 'jasmine-enzyme';
+
+import {mount, shallow} from 'enzyme';
+
+describe('QueryHistory', () => {
+  let historyWrapper;
+  beforeEach(() => {
+    jasmineEnzyme();
+    const historyCollection = new HistoryCollection([]);
+    historyWrapper = shallow(<QueryHistory historyCollection={historyCollection}/>);
+  });
+
+  describe('on construction', () => {
+    it('has no entries', (done) => {
+      let foundChildren = historyWrapper.find(QueryHistoryEntry);
+      expect(foundChildren.length).toBe(0);
+      done();
+    });
+  });
+
+  describe('when it has history', () => {
+    describe('when two SQL queries were executed', () => {
+      let foundChildren;
+
+      beforeEach(() => {
+        const historyObjects = [
+          {
+            query: 'second sql statement',
+            start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
+            status: false,
+            row_affected: 1,
+            total_time: '234 msec',
+            message: 'some other message',
+          },
+          {
+            query: 'first sql statement',
+            start_time: new Date(2017, 5, 3, 14, 3, 15, 150),
+            status: true,
+            row_affected: 2,
+            total_time: '14 msec',
+            message: 'a very important message',
+          },
+        ];
+        const historyCollection = new HistoryCollection(historyObjects);
+
+        historyWrapper = mount(<QueryHistory historyCollection={historyCollection}/>);
+
+        foundChildren = historyWrapper.find(QueryHistoryEntry);
+      });
+
+      it('has two query history entries', () => {
+        expect(foundChildren.length).toBe(2);
+      });
+
+      it('displays the SQL of the queries in order', () => {
+        expect(foundChildren.at(0).text()).toContain('first sql statement');
+        expect(foundChildren.at(1).text()).toContain('second sql statement');
+      });
+
+      it('displays the formatted timestamp of the queries in chronological order by most recent first', () => {
+        expect(foundChildren.at(0).text()).toContain('Jun 3 2017 – 14:03:15');
+        expect(foundChildren.at(1).text()).toContain('Dec 11 2016 – 01:33:05');
+      });
+
+      it('displays the number of rows affected', () => {
+        expect(foundChildren.at(1).text()).toContain('1 rows affected');
+        expect(foundChildren.at(0).text()).toContain('2 rows affected');
+      });
+
+      it('displays the total time', () => {
+        expect(foundChildren.at(0).text()).toContain('total time: 14 msec');
+        expect(foundChildren.at(1).text()).toContain('total time: 234 msec');
+      });
+
+      it('displays the truncated message', () => {
+        expect(foundChildren.at(0).text()).toContain('a very important message');
+        expect(foundChildren.at(1).text()).toContain('some other message');
+      });
+
+      describe('when there are one failing and one successful query each', () => {
+        it('adds a white background color for the successful query', () => {
+          expect(foundChildren.at(0).find('div').first()).toHaveStyle('backgroundColor', '#FFF');
+        });
+        it('adds a red background color for the failed query', () => {
+          expect(foundChildren.at(1).find('div').first()).toHaveStyle('backgroundColor', '#F7D0D5');
+        });
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index cf559c44..f3e7ed01 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -227,6 +227,7 @@ def create_constraint(
     except Exception:
         traceback.print_exc(file=sys.stderr)
 
+
 def create_debug_function(server, db_name, function_name="test_func"):
     try:
         connection = get_db_connection(db_name,
@@ -305,6 +306,7 @@ def drop_database(connection, database_name):
             connection.commit()
             connection.close()
 
+
 def drop_tablespace(connection):
     """This function used to drop the tablespace"""
     pg_cursor = connection.cursor()
diff --git a/web/webpack.config.js b/web/webpack.config.js
index 91586592..fc6d29e0 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -1,18 +1,21 @@
 /* eslint-env node */
 
 module.exports = {
-  context: __dirname + '/pgadmin/static/jsx',
-  entry: './components.jsx',
+  context: __dirname + '/pgadmin/static',
+  entry: {
+    reactComponents: './jsx/components.jsx',
+    history: './js/history/index.js',
+  },
   output: {
     libraryTarget: 'amd',
     path: __dirname + '/pgadmin/static/js/generated',
-    filename: 'reactComponents.js',
+    filename: '[name].js',
   },
 
   module: {
     rules: [{
       test: /\.jsx?$/,
-      exclude: /node_modules/,
+      exclude: [/node_modules/, /vendor/],
       use: {
         loader: 'babel-loader',
         options: {
diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js
index d1c6455a..6754f048 100644
--- a/web/webpack.test.config.js
+++ b/web/webpack.test.config.js
@@ -22,7 +22,7 @@ module.exports = {
         use: {
           loader: 'babel-loader',
           options: {
-            presets: ['es2015'],
+            presets: ['es2015', 'react'],
           },
         },
       },
@@ -51,6 +51,7 @@ module.exports = {
   },
 
   resolve: {
+    extensions: ['.js', '.jsx'],
     alias: {
       'alertify': sourcesDir + '/vendor/alertifyjs/alertify',
       'jquery': sourcesDir + '/vendor/jquery/jquery-1.11.2',
@@ -67,4 +68,11 @@ module.exports = {
       'pgadmin': sourcesDir + '/js/pgadmin',
     },
   },
+  externals: {
+    'react/addons': true,
+    'react/lib/ReactContext': true,
+    'react/lib/ExecutionEnvironment': true,
+    'react-dom/test-utils': true,
+    'react-test-renderer/shallow': true,
+  },
 };
diff --git a/web/yarn.lock b/web/yarn.lock
index b04caa5c..ce72d41b 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -1371,6 +1371,12 @@ decamelize@^1.0.0, decamelize@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
 
+deep-equal-ident@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz#06f4b89e53710cd6cea4a7781c7a956642de8dc9"
+  dependencies:
+    lodash.isequal "^3.0"
+
 deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
@@ -1608,6 +1614,12 @@ entities@^1.1.1, entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
+enzyme-matchers@^3.1.0, enzyme-matchers@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/enzyme-matchers/-/enzyme-matchers-3.2.0.tgz#4718779a3b9eb5e8ebad46804f8d3e66045d0181"
+  dependencies:
+    deep-equal-ident "^1.1.1"
+
 enzyme@~2.8.2:
   version "2.8.2"
   resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.8.2.tgz#6c8bcb05012abc4aa4bc3213fb23780b9b5b1714"
@@ -2299,6 +2311,12 @@ ignore@^3.2.0:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
 
+immutability-helper@^2.2.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.2.2.tgz#e7e9da728b3de2fad34a216f4157b326dbccc892"
+  dependencies:
+    invariant "^2.2.0"
+
 "imports-loader@git+https://github.com/webpack-contrib/imports-loader.git#44d6f48463b256a17c1ba6fd9b5cc1449b4e379d":
   version "0.7.1"
   resolved "git+https://github.com/webpack-contrib/imports-loader.git#44d6f48463b256a17c1ba6fd9b5cc1449b4e379d"
@@ -2568,6 +2586,12 @@ jasmine-core@~2.5.2:
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297"
 
+jasmine-enzyme@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/jasmine-enzyme/-/jasmine-enzyme-3.2.0.tgz#0eeb370d4fa965db03e04347ca9c4ed5a60fadc2"
+  dependencies:
+    enzyme-matchers "^3.2.0"
+
 jodid25519@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967"
@@ -2828,6 +2852,22 @@ loader-utils@^1.0.2:
     emojis-list "^2.0.0"
     json5 "^0.5.0"
 
+lodash._baseisequal@^3.0.0:
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz#d8025f76339d29342767dcc887ce5cb95a5b51f1"
+  dependencies:
+    lodash.isarray "^3.0.0"
+    lodash.istypedarray "^3.0.0"
+    lodash.keys "^3.0.0"
+
+lodash._bindcallback@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
+
+lodash._getnative@^3.0.0:
+  version "3.9.1"
+  resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+
 lodash.assignin@^4.0.9:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
@@ -2852,6 +2892,33 @@ lodash.foreach@^4.3.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
 
+lodash.isarguments@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+
+lodash.isarray@^3.0.0:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+
+lodash.isequal@^3.0:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-3.0.4.tgz#1c35eb3b6ef0cd1ff51743e3ea3cf7fdffdacb64"
+  dependencies:
+    lodash._baseisequal "^3.0.0"
+    lodash._bindcallback "^3.0.0"
+
+lodash.istypedarray@^3.0.0:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
+
+lodash.keys@^3.0.0:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+  dependencies:
+    lodash._getnative "^3.0.0"
+    lodash.isarguments "^3.0.0"
+    lodash.isarray "^3.0.0"
+
 lodash.map@^4.4.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
@@ -3013,6 +3080,10 @@ module-deps@^4.0.8:
     through2 "^2.0.0"
     xtend "^4.0.0"
 
+moment@^2.18.1:
+  version "2.18.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
+
 [email protected]:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@@ -3499,10 +3570,10 @@ randomatic@^1.1.3:
     kind-of "^3.0.2"
 
 randombytes@^2.0.0, randombytes@^2.0.1:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.4.tgz#9551df208422c8f80eb58e2326dd0b840ff22efd"
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79"
   dependencies:
-    safe-buffer "^5.0.1"
+    safe-buffer "^5.1.0"
 
 range-parser@^1.0.3, range-parser@^1.2.0:
   version "1.2.0"
@@ -3818,7 +3889,7 @@ rx-lite@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
 
-safe-buffer@^5.0.1:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223"
 


view thread (45+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: [pgAdmin4] [PATCH] History Tab rewrite in React
  In-Reply-To: <CACrUwhKPtLNc-J1uXE1eOZjrtRnTtqo_7YpZThNauvgw=HSwJQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox