mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Force update after thirty days since update was downloaded (#5776)
This commit is contained in:
1 parent
eb6cc70143
commit
6e5c8e0427
12 files changed
+1280
No files matched your search
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { rootFrameChildComponentInjectionToken } from "../../frames/root-frame/root-frame-child-component-injection-token";
|
||||
import { ForceUpdateModal } from "./force-update-modal";
|
||||
import timeSinceUpdateWasDownloadedInjectable from "./time-since-update-was-downloaded.injectable";
|
||||
import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.injectable";
|
||||
import timeAfterUpdateMustBeInstalledInjectable from "./time-after-update-must-be-installed.injectable";
|
||||
|
||||
const forceUpdateModalRootFrameComponentInjectable = getInjectable({
|
||||
id: "force-update-modal-root-frame-component",
|
||||
|
||||
instantiate: (di) => {
|
||||
const timeSinceUpdateWasDownloaded = di.inject(timeSinceUpdateWasDownloadedInjectable);
|
||||
const updateDownloadedDateTime = di.inject(updateDownloadedDateTimeInjectable);
|
||||
const timeWhenUpdateMustBeInstalled = di.inject(timeAfterUpdateMustBeInstalledInjectable);
|
||||
|
||||
return {
|
||||
id: "force-update-modal",
|
||||
Component: ForceUpdateModal,
|
||||
|
||||
shouldRender: computed(
|
||||
() =>
|
||||
!!updateDownloadedDateTime.value.get() &&
|
||||
timeSinceUpdateWasDownloaded.get() >= timeWhenUpdateMustBeInstalled,
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: rootFrameChildComponentInjectionToken,
|
||||
});
|
||||
|
||||
export default forceUpdateModalRootFrameComponentInjectable;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
$baseline: 8px;
|
||||
|
||||
.ForceUpdateModal {
|
||||
background-color: white;
|
||||
border-radius: 1 * $baseline;
|
||||
padding: 3 * $baseline;
|
||||
width: 50 * $baseline;
|
||||
|
||||
.header {
|
||||
margin-bottom: 2 * $baseline;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 2 * $baseline;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import restartAndInstallUpdateInjectable from "../../components/update-button/restart-and-install-update.injectable";
|
||||
import { Countdown } from "../../components/countdown/countdown";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import installUpdateCountdownInjectable from "./install-update-countdown.injectable";
|
||||
import { Dialog } from "../../components/dialog";
|
||||
import { Button } from "../../components/button";
|
||||
import styles from "./force-update-modal.module.scss";
|
||||
|
||||
interface Dependencies {
|
||||
restartAndInstallUpdate: () => void;
|
||||
secondsTill: IComputedValue<number>;
|
||||
}
|
||||
|
||||
const NonInjectedForceUpdateModal = observer(
|
||||
({ restartAndInstallUpdate, secondsTill }: Dependencies) => (
|
||||
<Dialog isOpen={true} pinned>
|
||||
<div
|
||||
data-testid="must-update-immediately"
|
||||
className={styles.ForceUpdateModal}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<h2>Please update</h2>
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<p>
|
||||
An update to Lens Desktop is required to continue using the application.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
primary
|
||||
data-testid="update-now-from-must-update-immediately-modal"
|
||||
onClick={restartAndInstallUpdate}
|
||||
label="Update"
|
||||
>
|
||||
{" "}
|
||||
(
|
||||
<Countdown
|
||||
secondsTill={secondsTill}
|
||||
data-testid="countdown-to-automatic-update"
|
||||
/>
|
||||
)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
),
|
||||
);
|
||||
|
||||
export const ForceUpdateModal = withInjectables<Dependencies>(
|
||||
NonInjectedForceUpdateModal,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
restartAndInstallUpdate: di.inject(restartAndInstallUpdateInjectable),
|
||||
secondsTill: di.inject(installUpdateCountdownInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import countdownStateInjectable from "../../components/countdown/countdown-state.injectable";
|
||||
import secondsAfterInstallStartsInjectable from "./seconds-after-install-starts.injectable";
|
||||
import restartAndInstallUpdateInjectable from "../../components/update-button/restart-and-install-update.injectable";
|
||||
|
||||
const installUpdateCountdownInjectable = getInjectable({
|
||||
id: "install-update-countdown",
|
||||
|
||||
instantiate: (di) => {
|
||||
const secondsAfterInstallStarts = di.inject(secondsAfterInstallStartsInjectable);
|
||||
const restartAndInstallUpdate = di.inject(restartAndInstallUpdateInjectable);
|
||||
|
||||
return di.inject(countdownStateInjectable, {
|
||||
startFrom: secondsAfterInstallStarts,
|
||||
|
||||
onZero: () => {
|
||||
restartAndInstallUpdate();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default installUpdateCountdownInjectable;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
const secondsAfterInstallStartsInjectable = getInjectable({
|
||||
id: "seconds-after-install-starts",
|
||||
instantiate: () => 90,
|
||||
});
|
||||
|
||||
export default secondsAfterInstallStartsInjectable;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const timeAfterUpdateMustBeInstalledInjectable = getInjectable({
|
||||
id: "time-after-update-must-be-installed",
|
||||
instantiate: () => THIRTY_DAYS,
|
||||
});
|
||||
|
||||
export default timeAfterUpdateMustBeInstalledInjectable;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import { computed } from "mobx";
|
||||
import moment from "moment";
|
||||
import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.injectable";
|
||||
import { reactiveNow } from "../../../common/utils/reactive-now/reactive-now";
|
||||
|
||||
const timeSinceUpdateWasDownloadedInjectable = getInjectable({
|
||||
id: "time-since-update-was-downloaded",
|
||||
|
||||
instantiate: (di) => {
|
||||
const updateDownloadedDateTime = di.inject(updateDownloadedDateTimeInjectable);
|
||||
|
||||
return computed(() => {
|
||||
const currentTimestamp = reactiveNow();
|
||||
|
||||
const downloadedAt = updateDownloadedDateTime.value.get();
|
||||
|
||||
assert(downloadedAt);
|
||||
|
||||
return currentTimestamp - (moment(downloadedAt).unix() * 1000);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default timeSinceUpdateWasDownloadedInjectable;
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import {
|
||||
computed,
|
||||
observable,
|
||||
onBecomeObserved,
|
||||
onBecomeUnobserved,
|
||||
runInAction,
|
||||
} from "mobx";
|
||||
|
||||
const countdownStateInjectable = getInjectable({
|
||||
id: "countdown-state",
|
||||
|
||||
instantiate: (
|
||||
di,
|
||||
{ startFrom, onZero }: { startFrom: number; onZero: () => void },
|
||||
) => {
|
||||
const state = observable.box(startFrom);
|
||||
|
||||
let intervalId: NodeJS.Timer | undefined;
|
||||
|
||||
const stop = () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
intervalId = setInterval(() => {
|
||||
const secondsLeft = state.get() - 1;
|
||||
|
||||
runInAction(() => {
|
||||
state.set(secondsLeft);
|
||||
});
|
||||
|
||||
if (secondsLeft === 0) {
|
||||
stop();
|
||||
onZero();
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
onBecomeObserved(state, start);
|
||||
onBecomeUnobserved(state, stop);
|
||||
|
||||
return computed(() => state.get());
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default countdownStateInjectable;
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import countdownStateInjectable from "./countdown-state.injectable";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import { Countdown } from "./countdown";
|
||||
import React from "react";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { advanceFakeTime, useFakeTime } from "../../../common/test-utils/use-fake-time";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observe } from "mobx";
|
||||
import { noop } from "../../../common/utils";
|
||||
|
||||
describe("countdown", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
useFakeTime("2015-10-21T07:28:00Z");
|
||||
|
||||
di = createContainer("irrelevant");
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
di.register(countdownStateInjectable);
|
||||
});
|
||||
|
||||
describe("when rendering countdown", () => {
|
||||
let rendered: RenderResult;
|
||||
let onZeroMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
onZeroMock = jest.fn();
|
||||
|
||||
const secondsTill = di.inject(countdownStateInjectable, {
|
||||
startFrom: 42,
|
||||
onZero: onZeroMock,
|
||||
});
|
||||
|
||||
rendered = render(
|
||||
<Countdown secondsTill={secondsTill} />,
|
||||
);
|
||||
});
|
||||
|
||||
it("renders with initial seconds", () => {
|
||||
expect(rendered.container).toHaveTextContent("42");
|
||||
});
|
||||
|
||||
describe("when time passes", () => {
|
||||
beforeEach(() => {
|
||||
advanceFakeTime(1000);
|
||||
});
|
||||
|
||||
it("updates the seconds", () => {
|
||||
expect(rendered.container).toHaveTextContent("41");
|
||||
});
|
||||
|
||||
it("does not call callback yet", () => {
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("when just not enough time passes to fulfill the countdown, does not call the callback yet", () => {
|
||||
advanceFakeTime(41 * 1000);
|
||||
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when time passes enough to fulfill the countdown", () => {
|
||||
beforeEach(() => {
|
||||
advanceFakeTime(42 * 1000);
|
||||
});
|
||||
|
||||
it("shows zero as seconds", () => {
|
||||
expect(rendered.container).toHaveTextContent("0");
|
||||
});
|
||||
|
||||
it("calls the callback", () => {
|
||||
expect(onZeroMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when time passes even more", () => {
|
||||
beforeEach(() => {
|
||||
onZeroMock.mockClear();
|
||||
|
||||
advanceFakeTime(1000);
|
||||
});
|
||||
|
||||
it("does not update the countdown anymore", () => {
|
||||
expect(rendered.container).toHaveTextContent("0");
|
||||
});
|
||||
|
||||
it("does not call the callback", () => {
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("given observed", () => {
|
||||
let onZeroMock: jest.Mock;
|
||||
let unobserve: () => void;
|
||||
let secondsTill: IComputedValue<number>;
|
||||
|
||||
beforeEach(() => {
|
||||
onZeroMock = jest.fn();
|
||||
|
||||
secondsTill = di.inject(countdownStateInjectable, {
|
||||
startFrom: 1,
|
||||
onZero: onZeroMock,
|
||||
});
|
||||
|
||||
unobserve = observe(secondsTill, noop);
|
||||
});
|
||||
|
||||
describe("given unobserved, when enough time passes so that it would fulfill the countdown", () => {
|
||||
beforeEach(() => {
|
||||
onZeroMock.mockClear();
|
||||
|
||||
unobserve();
|
||||
|
||||
advanceFakeTime(1000);
|
||||
});
|
||||
|
||||
it("does not call callback yet", () => {
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("given observed again, when time passes to fulfill the countdown, calls the callback", () => {
|
||||
observe(secondsTill, noop);
|
||||
|
||||
advanceFakeTime(1000);
|
||||
|
||||
expect(onZeroMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import type { HTMLAttributes } from "react";
|
||||
import React from "react";
|
||||
|
||||
interface CountdownProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
secondsTill: IComputedValue<number>;
|
||||
}
|
||||
|
||||
export const Countdown = observer(({ secondsTill, ...props }: CountdownProps) => (
|
||||
<span {...props}>{secondsTill.get()}</span>
|
||||
));
|
||||
Reference in new issue
Block a user