Skip to content

Commit a9d36cb

Browse files
committed
implement server and client version check for updates
1 parent 0bf4536 commit a9d36cb

File tree

8 files changed

+82
-6
lines changed

8 files changed

+82
-6
lines changed

build/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ COPY cmd ./cmd
1313
COPY pkg ./pkg
1414
COPY go.mod .
1515
COPY go.sum .
16-
RUN go build -o server ./cmd/playground && \
16+
RUN go build -o server -ldflags="-X 'main.Version=$APP_VERSION'" ./cmd/playground && \
1717
GOOS=js GOARCH=wasm go build -o ./worker.wasm ./cmd/webworker && \
1818
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
1919

cmd/playground/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"go.uber.org/zap"
1919
)
2020

21+
const Version = "testing"
22+
2123
type appArgs struct {
2224
packagesFile string
2325
addr string
@@ -75,6 +77,7 @@ func start(goRoot string, args appArgs) error {
7577
return fmt.Errorf("invalid cleanup interval parameter: %s", err)
7678
}
7779

80+
zap.S().Info("Server version: ", Version)
7881
zap.S().Infof("GOROOT is %q", goRoot)
7982
zap.S().Infof("Packages file is %q", args.packagesFile)
8083
zap.S().Infof("Cleanup interval is %s", cleanInterval.String())
@@ -94,7 +97,7 @@ func start(goRoot string, args appArgs) error {
9497
go store.StartCleaner(ctx, cleanInterval, nil)
9598

9699
r := mux.NewRouter()
97-
langserver.New(packages, compiler.NewBuildService(zap.S(), store)).
100+
langserver.New(Version, packages, compiler.NewBuildService(zap.S(), store)).
98101
Mount(r.PathPrefix("/api").Subrouter())
99102
r.PathPrefix("/").Handler(langserver.SpaFileServer("./public"))
100103

pkg/langserver/request.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ func (r *ErrorResponse) Write(w http.ResponseWriter) http.ResponseWriter {
5252
return w
5353
}
5454

55+
type VersionResponse struct {
56+
Version string `json:"version"`
57+
}
58+
59+
func (r VersionResponse) Write(w http.ResponseWriter) {
60+
WriteJSON(w, r)
61+
}
62+
5563
type SuggestionsResponse struct {
5664
Suggestions []*analyzer.CompletionItem `json:"suggestions"`
5765
}

pkg/langserver/server.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ const (
2929
)
3030

3131
type Service struct {
32+
version string
3233
log *zap.SugaredLogger
3334
index analyzer.PackageIndex
3435
compiler compiler.BuildService
3536
limiter *rate.Limiter
3637
}
3738

38-
func New(packages []*analyzer.Package, builder compiler.BuildService) *Service {
39+
func New(version string, packages []*analyzer.Package, builder compiler.BuildService) *Service {
3940
return &Service{
4041
compiler: builder,
42+
version: version,
4143
log: zap.S().Named("langserver"),
4244
index: analyzer.BuildPackageIndex(packages),
4345
limiter: rate.NewLimiter(rate.Every(frameTime), compileRequestsPerFrame),
@@ -46,6 +48,8 @@ func New(packages []*analyzer.Package, builder compiler.BuildService) *Service {
4648

4749
// Mount mounts service on route
4850
func (s *Service) Mount(r *mux.Router) {
51+
r.Path("/version").
52+
HandlerFunc(WrapHandler(s.HandleGetVersion))
4953
r.Path("/suggest").
5054
HandlerFunc(WrapHandler(s.HandleGetSuggestion))
5155
r.Path("/run").Methods(http.MethodPost).
@@ -113,6 +117,11 @@ func (s *Service) provideSuggestion(req SuggestionRequest) (*SuggestionsResponse
113117
return s.lookupBuiltin(req.Value)
114118
}
115119

120+
func (s *Service) HandleGetVersion(w http.ResponseWriter, r *http.Request) error {
121+
WriteJSON(w, VersionResponse{Version: s.version})
122+
return nil
123+
}
124+
116125
func (s *Service) HandleGetSuggestion(w http.ResponseWriter, r *http.Request) error {
117126
q := r.URL.Query()
118127
value := q.Get("value")

web/src/Header.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@
77
min-height: 44px;
88
}
99

10+
.app__update {
11+
display: none;
12+
position: absolute;
13+
top: 50%;
14+
left: 50%;
15+
transform: translate(-50%, -50%);
16+
width: 100%;
17+
max-width: 500px;
18+
z-index: 9999;
19+
}
20+
21+
.app__update.app__update--visible {
22+
display: block
23+
}
24+
1025
.header__preloader {
1126
margin-right: 15px;
1227
}

web/src/Header.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import React from 'react';
22
import './Header.css'
3+
import { MessageBarButton } from 'office-ui-fabric-react';
34
import {CommandBar, ICommandBarItemProps} from 'office-ui-fabric-react/lib/CommandBar';
5+
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
46
import {getTheme} from '@uifabric/styling';
57
import SettingsModal, {SettingsChanges} from './settings/SettingsModal';
68
import AboutModal from './AboutModal';
79
import config from './services/config';
10+
import api from './services/api';
811
import { getSnippetsMenuItems, SnippetMenuItem } from './utils/headerutils';
912
import {
1013
Connect,
@@ -19,13 +22,12 @@ import {
1922
} from './store';
2023
import ChangeLogModal from "./ChangeLogModal";
2124

22-
23-
2425
interface HeaderState {
2526
showSettings: boolean
2627
showAbout: boolean
2728
showChangelog: boolean
2829
loading: boolean
30+
showUpdateBanner: boolean
2931
}
3032

3133
@Connect(s => ({darkMode: s.settings.darkMode, loading: s.status?.loading}))
@@ -39,7 +41,8 @@ export class Header extends React.Component<any, HeaderState> {
3941
showSettings: false,
4042
showAbout: false,
4143
showChangelog: false,
42-
loading: false
44+
loading: false,
45+
showUpdateBanner: false
4346
};
4447
}
4548

@@ -49,6 +52,13 @@ export class Header extends React.Component<any, HeaderState> {
4952
fileElement.accept = '.go';
5053
fileElement.addEventListener('change', () => this.onItemSelect(), false);
5154
this.fileInput = fileElement;
55+
56+
// show update popover
57+
api.getVersion().then(r => {
58+
const {version} = r;
59+
if (!version) return;
60+
this.setState({showUpdateBanner: version !== config.appVersion});
61+
}).catch(err => console.warn('failed to check server API version: ', err));
5262
}
5363

5464
onItemSelect() {
@@ -208,6 +218,23 @@ export class Header extends React.Component<any, HeaderState> {
208218

209219
render() {
210220
return <header className='header' style={this.styles}>
221+
<MessageBar
222+
className={this.state.showUpdateBanner ? 'app__update app__update--visible' : 'app__update'}
223+
messageBarType={MessageBarType.warning}
224+
onDismiss={() => this.setState({showUpdateBanner: false})}
225+
dismissButtonAriaLabel="Close"
226+
isMultiline={false}
227+
actions={
228+
<div>
229+
<MessageBarButton onClick={() => {
230+
this.setState({showUpdateBanner: false});
231+
config.forceRefreshPage();
232+
}}>Action</MessageBarButton>
233+
</div>
234+
}
235+
>
236+
Web application was updated, click <b>Reload</b> to apply changes
237+
</MessageBar>
211238
<img
212239
src='/go-logo-blue.svg'
213240
className='header__logo'

web/src/services/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,15 @@ export interface BuildResponse {
3636
fileName: string
3737
}
3838

39+
export interface VersionResponse {
40+
version: string
41+
}
42+
3943
export interface IAPIClient {
4044
readonly axiosClient: AxiosInstance
4145

46+
getVersion(): Promise<VersionResponse>
47+
4248
getSuggestions(query: { packageName?: string, value?: string }): Promise<monaco.languages.CompletionList>
4349

4450
evaluateCode(code: string, format: boolean): Promise<RunResponse>
@@ -71,6 +77,10 @@ class Client implements IAPIClient {
7177
constructor(private client: axios.AxiosInstance) {
7278
}
7379

80+
async getVersion(): Promise<VersionResponse> {
81+
return this.get<VersionResponse>(`/version?=${Date.now()}`)
82+
}
83+
7484
async getSuggestions(query: { packageName?: string, value?: string }): Promise<monaco.languages.CompletionList> {
7585
const queryParams = Object.keys(query).map(k => `${k}=${query[k]}`).join('&');
7686
return this.get<monaco.languages.CompletionList>(`/suggest?${queryParams}`);

web/src/services/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,9 @@ export default {
139139

140140
sync() {
141141
setThemeStyles(this.darkThemeEnabled);
142+
},
143+
144+
forceRefreshPage() {
145+
document.location.reload(true);
142146
}
143147
};

0 commit comments

Comments
 (0)