Implement client-side version checking (#79)

Previously Conduit would render an iframe, received from
versioncheck.conduit.io.

Modify the client to retrieve the latest released version, via CORS.

Signed-off-by: Andrew Seigner <andrew@sig.gy>
This commit is contained in:
Andrew Seigner 2018-01-02 16:07:50 -08:00 committed by GitHub
parent ce69c2e534
commit 449f306aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 243 additions and 26 deletions

View File

@ -43,6 +43,13 @@ And then set the `-webpack-dev-server` flag when running the web server:
go run main.go -webpack-dev-server=http://localhost:8080
```
To add a JS dependency:
```
cd web/app
yarn add [dep]
```
## Run docker-compose
You can also run all of the go apps in a docker-compose environment.

View File

@ -65,24 +65,6 @@
& .ant-menu-item-active, & .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background: #007EFF;
}
& .conduit-current-version {
border: none;
margin: 0;
padding: 0 0 0 9px;
position: fixed;
height: 40px;
width: 150px;
bottom: calc(var(--base-width)*3);
color: white;
font-weight: 700;
}
& .conduit-version-check {
border: none;
margin: 0;
padding: 0;
}
}
.call-to-action .action {

View File

@ -7,7 +7,7 @@
--darkblue: #071E3C;
--warmgrey: #f6f6f6;
--coldgrey: #c9c9c9;
--latency-p99: #2F80ED;
--latency-p99: var(--messageblue);
--latency-p95: #2D9CDB;
--latency-p50: #56CCF2;
--subheader-grey: #828282;
@ -185,3 +185,40 @@ tr th.numeric, .numeric {
text-decoration-style: dotted;
}
}
/* from https://conduit.io/ */
a.button {
color: var(--messageblue);
background: #fff;
border-radius: 48px;
border: 1px solid var(--messageblue);
padding: 10px 14px 11px 14px;
font-weight: bold;
font-size: 14px;
margin: 0px 8px 8px 0px;
position: relative;
line-height: 4;
}
a.button:hover {
top: 1px;
background-color: #efefef;
text-decoration: none;
}
a.button:active {
background-color: var(--messageblue);
color: #fff;
}
a.button.primary {
color: #fff;
background-color: var(--messageblue);
}
a.button.primary:hover {
top: 1px;
background-color: #2875DE;
text-decoration: none;
}
a.button.primary:active {
background-color: #2469C7;
}

11
web/app/css/version.css Normal file
View File

@ -0,0 +1,11 @@
@import 'styles.css';
.version {
padding: 0 0 0 9px;
font-size: 13px;
font-weight: var(--font-weight-bold);
line-height: 2;
position: fixed;
height: 40px;
bottom: calc(var(--base-width)*10);
}

View File

@ -2,6 +2,7 @@ import { Link } from 'react-router-dom';
import logo from './../../img/reversed_logo.png';
import { Menu } from 'antd';
import React from 'react';
import Version from './Version.jsx';
import './../../css/sidebar.css';
export default class Sidebar extends React.Component {
@ -28,13 +29,9 @@ export default class Sidebar extends React.Component {
<Link to="https://conduit.io/docs/" target="_blank">Documentation</Link>
</Menu.Item>
</Menu>
<div className="conduit-current-version">
Running {this.props.releaseVersion}<br />
<iframe
className="conduit-version-check"
src={`https://versioncheck.conduit.io/version/${this.props.releaseVersion}?uuid=${this.props.uuid}`}
sandbox="allow-top-navigation" />
</div>
<Version
releaseVersion={this.props.releaseVersion}
uuid={this.props.uuid} />
</div>
</div>
);

View File

@ -0,0 +1,80 @@
import { ApiHelpers } from './util/ApiHelpers.js';
import { Link } from 'react-router-dom';
import React from 'react';
import './../../css/version.css';
export default class Version extends React.Component {
constructor(props) {
super(props);
this.loadFromServer = this.loadFromServer.bind(this);
this.handleApiError = this.handleApiError.bind(this);
this.state = {
error: null,
latest: null,
loaded: false,
pendingRequests: false
};
}
componentDidMount() {
this.loadFromServer();
}
loadFromServer() {
if (this.state.pendingRequests) {
return; // don't make more requests if the ones we sent haven't completed
}
this.setState({ pendingRequests: true });
let versionUrl = `https://versioncheck.conduit.io/version.json?version=${this.props.releaseVersion}?uuid=${this.props.uuid}`;
let versionFetch = ApiHelpers("").fetch(versionUrl);
// expose serverPromise for testing
this.serverPromise = Promise.all([versionFetch])
.then(([resp]) => {
this.setState({
latest: resp.version,
loaded: true,
pendingRequests: false,
});
}).catch(this.handleApiError);
}
handleApiError(e) {
this.setState({
loaded: true,
pendingRequests: false,
error: e
});
}
renderVersionCheck() {
if (!this.state.loaded || this.state.pendingRequests) {
return "Performing version check...";
}
if (!this.state.latest) {
return (<div>
Version check failed
{this.state.error ? ": "+this.state.error : null}
</div>);
} else if (this.state.latest === this.props.releaseVersion) {
return "Conduit is up to date";
} else {
return (<div>
A new version ({this.state.latest}) is available<br />
<Link to="https://versioncheck.conduit.io/update" className="button primary" target="_blank">Update Now</Link>
</div>);
}
}
render() {
return (
<div className="version">
Currently running Conduit {this.props.releaseVersion}<br />
{this.renderVersionCheck()}
</div>
);
}
}

View File

@ -0,0 +1,103 @@
import { BrowserRouter } from 'react-router-dom';
import { expect } from 'chai';
import { mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import sinonStubPromise from 'sinon-stub-promise';
import Version from '../js/components/Version.jsx';
sinonStubPromise(sinon);
describe('Version', () => {
let curVer = "v1.2.3";
let newVer = "v2.3.4";
let component, fetchStub;
function withPromise(fn) {
return component.find("Version").get(0).serverPromise.then(fn);
}
beforeEach(() => {
fetchStub = sinon.stub(window, 'fetch');
});
afterEach(() => {
window.fetch.restore();
});
it('renders initial loading message', () => {
fetchStub.returnsPromise().resolves({
ok: true,
});
component = mount(
<BrowserRouter>
<Version />
</BrowserRouter>
);
expect(component.find("Version")).to.have.length(1);
expect(component.html()).to.include("Performing version check...");
});
it('renders up to date message when versions match', () => {
fetchStub.returnsPromise().resolves({
ok: true,
json: () => Promise.resolve({ version: curVer })
});
component = mount(
<BrowserRouter>
<Version
releaseVersion={curVer}
uuid="fakeuuid" />
</BrowserRouter>
);
return withPromise(() => {
expect(component.html()).to.include("Conduit is up to date");
});
});
it('renders update message when versions do not match', () => {
fetchStub.returnsPromise().resolves({
ok: true,
json: () => Promise.resolve({ version: newVer })
});
component = mount(
<BrowserRouter>
<Version
releaseVersion={curVer}
uuid="fakeuuid" />
</BrowserRouter>
);
return withPromise(() => {
expect(component.html()).to.include("A new version (");
expect(component.html()).to.include(newVer);
expect(component.html()).to.include(") is available");
});
});
it('renders error when version check fails', () => {
let errMsg = "Fake error";
fetchStub.returnsPromise().resolves({
ok: false,
statusText: errMsg
});
component = mount(
<BrowserRouter>
<Version />
</BrowserRouter>
);
return withPromise(() => {
expect(component.html()).to.include("Version check failed");
expect(component.html()).to.include(errMsg);
});
});
});