Skip to content

Commit 0f66b6e

Browse files
authored
Deploy button front (streamlit#2552)
* Python side * format * import * todo * # * git binary or GitPython not installed * undo white spaces * undo white spaces * undo white spaces * handle_git_information_request * git_info_changed * Using enum * ahead_commits and uncommitted_files as properties * format * Frontend changes * Working with frontend changes * Deprecating deployParams * Unit tests working * deploy * snapshots * Fixing spec, and updating snapshot * Fixing test
1 parent 6297cd3 commit 0f66b6e

32 files changed

+730
-47
lines changed

e2e/specs/st_main_menu.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("main menu", () => {
3333
cy.get('[data-testid="main-menu-popover"]').invoke(
3434
"attr",
3535
"style",
36-
"transform: translate3d(20px, 20px, 0px)"
36+
"transform: translate3d(20px, 20px, 0px);"
3737
);
3838
cy.get('[data-testid="main-menu-list"]').matchImageSnapshot("main_menu");
3939

15.9 KB
Loading
Binary file not shown.

frontend/src/App.tsx

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ import {
5050
ForwardMsgMetadata,
5151
Initialize,
5252
NewReport,
53-
IDeployParams,
5453
PageConfig,
5554
PageInfo,
5655
SessionEvent,
5756
WidgetStates,
5857
SessionState,
5958
Config,
59+
IGitInfo,
60+
GitInfo,
6061
} from "autogen/proto"
6162

6263
import { RERUN_PROMPT_MODAL_DIALOG } from "lib/baseconsts"
@@ -101,7 +102,7 @@ interface State {
101102
layout: PageConfig.Layout
102103
initialSidebarState: PageConfig.SidebarState
103104
allowRunOnSave: boolean
104-
deployParams?: IDeployParams | null
105+
gitInfo?: IGitInfo | null
105106
}
106107

107108
const ELEMENT_LIST_BUFFER_TIMEOUT_MS = 10
@@ -158,7 +159,7 @@ export class App extends PureComponent<Props, State> {
158159
layout: PageConfig.Layout.CENTERED,
159160
initialSidebarState: PageConfig.SidebarState.AUTO,
160161
allowRunOnSave: true,
161-
deployParams: null,
162+
gitInfo: null,
162163
}
163164

164165
this.sessionEventDispatcher = new SessionEventDispatcher()
@@ -235,6 +236,21 @@ export class App extends PureComponent<Props, State> {
235236
this.openDialog(newDialog)
236237
}
237238

239+
showDeployError = (
240+
title: string,
241+
errorNode: ReactNode,
242+
onContinue?: () => void
243+
): void => {
244+
this.openDialog({
245+
type: DialogType.DEPLOY_ERROR,
246+
title,
247+
msg: errorNode,
248+
onContinue,
249+
onClose: () => {},
250+
onTryAgain: this.sendLoadGitInfoBackMsg,
251+
})
252+
}
253+
238254
/**
239255
* Checks if the code version from the backend is different than the frontend
240256
*/
@@ -279,6 +295,12 @@ export class App extends PureComponent<Props, State> {
279295
}
280296
}
281297

298+
handleGitInfoChanged = (gitInfo: IGitInfo): void => {
299+
this.setState({
300+
gitInfo,
301+
})
302+
}
303+
282304
/**
283305
* Callback when we get a message from the server.
284306
*/
@@ -310,6 +332,8 @@ export class App extends PureComponent<Props, State> {
310332
this.handlePageConfigChanged(pageConfig),
311333
pageInfoChanged: (pageInfo: PageInfo) =>
312334
this.handlePageInfoChanged(pageInfo),
335+
gitInfoChanged: (gitInfo: GitInfo) =>
336+
this.handleGitInfoChanged(gitInfo),
313337
reportFinished: (status: ForwardMsg.ReportFinishedStatus) =>
314338
this.handleReportFinished(status),
315339
uploadReportProgress: (progress: number) =>
@@ -494,12 +518,7 @@ export class App extends PureComponent<Props, State> {
494518
}
495519

496520
const { reportHash } = this.state
497-
const {
498-
reportId,
499-
name: reportName,
500-
scriptPath,
501-
deployParams,
502-
} = newReportProto
521+
const { reportId, name: reportName, scriptPath } = newReportProto
503522

504523
const newReportHash = hashString(
505524
SessionInfo.current.installationId + scriptPath
@@ -517,10 +536,9 @@ export class App extends PureComponent<Props, State> {
517536
if (reportHash === newReportHash) {
518537
this.setState({
519538
reportId,
520-
deployParams,
521539
})
522540
} else {
523-
this.clearAppState(newReportHash, reportId, reportName, deployParams)
541+
this.clearAppState(newReportHash, reportId, reportName)
524542
}
525543
}
526544

@@ -594,15 +612,13 @@ export class App extends PureComponent<Props, State> {
594612
clearAppState(
595613
reportHash: string,
596614
reportId: string,
597-
reportName: string,
598-
deployParams?: IDeployParams | null
615+
reportName: string
599616
): void {
600617
this.setState(
601618
{
602619
reportId,
603620
reportName,
604621
reportHash,
605-
deployParams,
606622
elements: ReportRoot.empty(),
607623
},
608624
() => {
@@ -762,6 +778,19 @@ export class App extends PureComponent<Props, State> {
762778
this.widgetMgr.sendUpdateWidgetsMessage()
763779
}
764780

781+
sendLoadGitInfoBackMsg = (): void => {
782+
if (!this.isServerConnected()) {
783+
logError("Cannot load git information when disconnected from server.")
784+
return
785+
}
786+
787+
this.sendBackMsg(
788+
new BackMsg({
789+
loadGitInfo: true,
790+
})
791+
)
792+
}
793+
765794
sendRerunBackMsg = (widgetStates?: WidgetStates | undefined): void => {
766795
const { queryParams } = this.props.s4aCommunication.currentState
767796

@@ -899,7 +928,6 @@ export class App extends PureComponent<Props, State> {
899928
const {
900929
allowRunOnSave,
901930
connectionState,
902-
deployParams,
903931
dialog,
904932
elements,
905933
initialSidebarState,
@@ -909,6 +937,7 @@ export class App extends PureComponent<Props, State> {
909937
reportRunState,
910938
sharingEnabled,
911939
userSettings,
940+
gitInfo,
912941
} = this.state
913942
const outerDivClass = classNames("stApp", {
914943
"streamlit-embedded": isEmbeddedInIFrame(),
@@ -958,7 +987,13 @@ export class App extends PureComponent<Props, State> {
958987
screenCastState={this.props.screenCast.currentState}
959988
s4aMenuItems={this.props.s4aCommunication.currentState.items}
960989
sendS4AMessage={this.props.s4aCommunication.sendMessage}
961-
deployParams={deployParams}
990+
gitInfo={gitInfo}
991+
showDeployError={this.showDeployError}
992+
closeDialog={this.closeDialog}
993+
isDeployErrorModalOpen={
994+
this.state.dialog?.type === DialogType.DEPLOY_ERROR
995+
}
996+
loadGitInfo={this.sendLoadGitInfoBackMsg}
962997
/>
963998
</Header>
964999

frontend/src/components/core/MainMenu/MainMenu.test.tsx

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,23 @@
1818
import React from "react"
1919
import { shallow } from "lib/test_util"
2020
import { IMenuItem } from "hocs/withS4ACommunication/types"
21+
import { Args as SessionInfoArgs, SessionInfo } from "lib/SessionInfo"
22+
23+
import { GitInfo, IGitInfo } from "autogen/proto"
24+
import { IDeployErrorDialog } from "components/core/StreamlitDialog/DeployErrorDialogs/types"
25+
import {
26+
DetachedHead,
27+
ModuleIsNotAdded,
28+
NoRepositoryDetected,
29+
RepoIsAhead,
30+
UncommittedChanges,
31+
UntrackedFiles,
32+
} from "components/core/StreamlitDialog/DeployErrorDialogs"
2133

2234
import MainMenu, { Props } from "./MainMenu"
2335

36+
const { GitStates } = GitInfo
37+
2438
const getProps = (extend?: Partial<Props>): Props => ({
2539
aboutCallback: jest.fn(),
2640
clearCacheCallback: jest.fn(),
@@ -33,10 +47,29 @@ const getProps = (extend?: Partial<Props>): Props => ({
3347
settingsCallback: jest.fn(),
3448
shareCallback: jest.fn(),
3549
sharingEnabled: false,
50+
isDeployErrorModalOpen: false,
51+
showDeployError: jest.fn(),
52+
loadGitInfo: jest.fn(),
53+
closeDialog: jest.fn(),
3654
...extend,
3755
})
3856

3957
describe("App", () => {
58+
beforeAll(() => {
59+
SessionInfo.current = new SessionInfo({
60+
sessionId: "sessionId",
61+
streamlitVersion: "sv",
62+
pythonVersion: "pv",
63+
installationId: "iid",
64+
installationIdV1: "iid1",
65+
installationIdV2: "iid2",
66+
authorEmail: "ae",
67+
maxCachedMessageAge: 2,
68+
commandLine: "command line",
69+
userMapboxToken: "mpt",
70+
} as SessionInfoArgs)
71+
})
72+
4073
it("renders without crashing", () => {
4174
const props = getProps()
4275
const wrapper = shallow(<MainMenu {...props} />)
@@ -127,7 +160,7 @@ describe("App", () => {
127160
})
128161

129162
it("should render deploy app menu item", () => {
130-
const props = getProps({ deployParams: {} })
163+
const props = getProps({ gitInfo: {} })
131164
const wrapper = shallow(<MainMenu {...props} />)
132165
const popoverContent = wrapper.find("StatefulPopover").prop("content")
133166
// @ts-ignore
@@ -152,4 +185,121 @@ describe("App", () => {
152185
"About",
153186
])
154187
})
188+
189+
describe("Onclick deploy button", () => {
190+
function testDeployErrorModal(
191+
gitInfo: Partial<IGitInfo>,
192+
dialogComponent: (module: string) => IDeployErrorDialog
193+
): void {
194+
const props = getProps({
195+
gitInfo,
196+
})
197+
const wrapper = shallow(<MainMenu {...props} />)
198+
const popoverContent = wrapper.find("StatefulPopover").prop("content")
199+
// @ts-ignore
200+
const menuWrapper = shallow(popoverContent(() => {})).dive()
201+
202+
const items: any = menuWrapper.prop("items")
203+
204+
const deployOption = items.find(
205+
// @ts-ignore
206+
({ label }) => label === "Deploy this app"
207+
)
208+
209+
deployOption.onClick()
210+
211+
// @ts-ignore
212+
const dialog = dialogComponent(props.gitInfo.module)
213+
214+
expect(props.showDeployError.mock.calls[0][0]).toStrictEqual(
215+
dialog.title
216+
)
217+
expect(props.showDeployError.mock.calls[0][1]).toStrictEqual(dialog.body)
218+
}
219+
220+
it("no repo or remote", () => {
221+
testDeployErrorModal(
222+
{
223+
state: GitStates.DEFAULT,
224+
},
225+
NoRepositoryDetected
226+
)
227+
})
228+
229+
it("empty repo", () => {
230+
testDeployErrorModal(
231+
{
232+
repository: "",
233+
branch: "",
234+
module: "",
235+
state: GitStates.DEFAULT,
236+
},
237+
NoRepositoryDetected
238+
)
239+
})
240+
241+
it("repo is detached", () => {
242+
testDeployErrorModal(
243+
{
244+
repository: "repo",
245+
branch: "branch",
246+
module: "module",
247+
state: GitStates.HEAD_DETACHED,
248+
},
249+
DetachedHead
250+
)
251+
})
252+
253+
it("script was not added to the repo", () => {
254+
testDeployErrorModal(
255+
{
256+
repository: "repo",
257+
branch: "branch",
258+
module: "module.py",
259+
isHeadDetached: false,
260+
untrackedFiles: ["module.py"],
261+
},
262+
ModuleIsNotAdded
263+
)
264+
})
265+
266+
it("uncommitted changes", () => {
267+
testDeployErrorModal(
268+
{
269+
repository: "repo",
270+
branch: "branch",
271+
module: "module.py",
272+
isHeadDetached: false,
273+
uncommittedFiles: ["module.py"],
274+
untrackedFiles: [],
275+
},
276+
UncommittedChanges
277+
)
278+
})
279+
280+
it("changes not pushed to Github", () => {
281+
const deployParams: IGitInfo = {
282+
repository: "repo",
283+
branch: "branch",
284+
module: "module.py",
285+
uncommittedFiles: [],
286+
untrackedFiles: [],
287+
state: GitStates.AHEAD_OF_REMOTE,
288+
}
289+
testDeployErrorModal(deployParams, RepoIsAhead)
290+
})
291+
292+
it("untracked files", () => {
293+
testDeployErrorModal(
294+
{
295+
repository: "repo",
296+
branch: "branch",
297+
module: "module.py",
298+
isHeadDetached: false,
299+
untrackedFiles: ["another-file.py"],
300+
},
301+
UntrackedFiles
302+
)
303+
})
304+
})
155305
})

0 commit comments

Comments
 (0)