|
|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useEffect, useMemo, useState } from "react";
|
|
|
+import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
import { FormProvider, useForm } from "react-hook-form";
|
|
|
import {
|
|
|
PorterAppFormData,
|
|
|
@@ -13,7 +13,10 @@ import TabSelector from "components/TabSelector";
|
|
|
import { useHistory } from "react-router";
|
|
|
import { match } from "ts-pattern";
|
|
|
import Overview from "./tabs/Overview";
|
|
|
-import { useAppValidation } from "lib/hooks/useAppValidation";
|
|
|
+import {
|
|
|
+ AppValidationResult,
|
|
|
+ useAppValidation,
|
|
|
+} from "lib/hooks/useAppValidation";
|
|
|
import api from "shared/api";
|
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
|
import Settings from "./tabs/Settings";
|
|
|
@@ -32,6 +35,7 @@ import EventFocusView from "./tabs/activity-feed/events/focus-views/EventFocusVi
|
|
|
import { z } from "zod";
|
|
|
import { PorterApp } from "@porter-dev/api-contracts";
|
|
|
import JobsTab from "./tabs/JobsTab";
|
|
|
+import ConfirmRedeployModal from "./ConfirmRedeployModal";
|
|
|
|
|
|
// commented out tabs are not yet implemented
|
|
|
// will be included as support is available based on data from app revisions rather than helm releases
|
|
|
@@ -58,7 +62,7 @@ type AppDataContainerProps = {
|
|
|
const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
const history = useHistory();
|
|
|
const queryClient = useQueryClient();
|
|
|
- const [redeployOnSave, setRedeployOnSave] = useState(false);
|
|
|
+ const [confirmDeployModalOpen, setConfirmDeployModalOpen] = useState(false);
|
|
|
|
|
|
const {
|
|
|
porterApp,
|
|
|
@@ -158,13 +162,26 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
return dirty.every((f) => f === "expanded" || f === "id");
|
|
|
}, [isDirty, JSON.stringify(dirtyFields)]);
|
|
|
|
|
|
+ const buildIsDirty = useMemo(() => {
|
|
|
+ if (!isDirty) return false;
|
|
|
+
|
|
|
+ // get all entries in entire dirtyFields object that are true
|
|
|
+ const dirty = getAllDirtyFields(dirtyFields.app?.build ?? {});
|
|
|
+ return dirty.some((f) => f);
|
|
|
+ }, [isDirty, JSON.stringify(dirtyFields)]);
|
|
|
+
|
|
|
const onSubmit = handleSubmit(async (data) => {
|
|
|
try {
|
|
|
- const { validatedAppProto, variables, secrets } = await validateApp(
|
|
|
+ const { variables, secrets, validatedAppProto } = await validateApp(
|
|
|
data,
|
|
|
latestProto
|
|
|
);
|
|
|
|
|
|
+ if (buildIsDirty && !data.redeployOnSave) {
|
|
|
+ setConfirmDeployModalOpen(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// updates the default env group associated with this app to store app specific env vars
|
|
|
const res = await api.updateEnvironmentGroupV2(
|
|
|
"<token>",
|
|
|
@@ -213,11 +230,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- if (
|
|
|
- redeployOnSave &&
|
|
|
- latestSource.type === "github" &&
|
|
|
- dirtyFields.app?.build
|
|
|
- ) {
|
|
|
+ if (latestSource.type === "github" && buildIsDirty) {
|
|
|
const res = await api.reRunGHWorkflow(
|
|
|
"<token>",
|
|
|
{},
|
|
|
@@ -235,8 +248,6 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
if (res.data != null) {
|
|
|
window.open(res.data, "_blank", "noreferrer");
|
|
|
}
|
|
|
-
|
|
|
- setRedeployOnSave(false);
|
|
|
}
|
|
|
|
|
|
await queryClient.invalidateQueries([
|
|
|
@@ -260,6 +271,31 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
} catch (err) {}
|
|
|
});
|
|
|
|
|
|
+ const cancelRedeploy = useCallback(() => {
|
|
|
+ reset({
|
|
|
+ app: clientAppFromProto({
|
|
|
+ proto: previewRevision
|
|
|
+ ? PorterApp.fromJsonString(atob(previewRevision.b64_app_proto))
|
|
|
+ : latestProto,
|
|
|
+ overrides: servicesFromYaml,
|
|
|
+ variables: appEnv?.variables,
|
|
|
+ secrets: appEnv?.secret_variables,
|
|
|
+ }),
|
|
|
+ source: latestSource,
|
|
|
+ deletions: {
|
|
|
+ envGroupNames: [],
|
|
|
+ serviceNames: [],
|
|
|
+ },
|
|
|
+ redeployOnSave: false,
|
|
|
+ });
|
|
|
+ setConfirmDeployModalOpen(false);
|
|
|
+ }, [previewRevision, latestProto, servicesFromYaml, appEnv, latestSource]);
|
|
|
+
|
|
|
+ const finalizeDeploy = useCallback(() => {
|
|
|
+ setConfirmDeployModalOpen(false);
|
|
|
+ onSubmit();
|
|
|
+ }, [onSubmit, setConfirmDeployModalOpen]);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
reset({
|
|
|
app: clientAppFromProto({
|
|
|
@@ -275,6 +311,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
envGroupNames: [],
|
|
|
serviceNames: [],
|
|
|
},
|
|
|
+ redeployOnSave: false,
|
|
|
});
|
|
|
}, [
|
|
|
servicesFromYaml,
|
|
|
@@ -306,7 +343,12 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
loadingText={"Updating..."}
|
|
|
height={"10px"}
|
|
|
status={isSubmitting ? "loading" : ""}
|
|
|
- disabled={isSubmitting}
|
|
|
+ disabled={
|
|
|
+ isSubmitting ||
|
|
|
+ latestRevision.status === "CREATED" ||
|
|
|
+ latestRevision.status === "AWAITING_BUILD_ARTIFACT"
|
|
|
+ }
|
|
|
+ disabledTooltipMessage="Please wait for the build to complete before updating the app"
|
|
|
>
|
|
|
<Icon src={save} height={"13px"} />
|
|
|
<Spacer inline x={0.5} />
|
|
|
@@ -353,12 +395,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
{match(currentTab)
|
|
|
.with("activity", () => <Activity />)
|
|
|
.with("overview", () => <Overview />)
|
|
|
- .with("build-settings", () => (
|
|
|
- <BuildSettings
|
|
|
- redeployOnSave={redeployOnSave}
|
|
|
- setRedeployOnSave={setRedeployOnSave}
|
|
|
- />
|
|
|
- ))
|
|
|
+ .with("build-settings", () => <BuildSettings />)
|
|
|
.with("environment", () => (
|
|
|
<Environment latestSource={latestSource} />
|
|
|
))
|
|
|
@@ -370,6 +407,13 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
.otherwise(() => null)}
|
|
|
<Spacer y={2} />
|
|
|
</form>
|
|
|
+ {confirmDeployModalOpen ? (
|
|
|
+ <ConfirmRedeployModal
|
|
|
+ setOpen={setConfirmDeployModalOpen}
|
|
|
+ cancelRedeploy={cancelRedeploy}
|
|
|
+ finalizeDeploy={finalizeDeploy}
|
|
|
+ />
|
|
|
+ ) : null}
|
|
|
</FormProvider>
|
|
|
);
|
|
|
};
|