1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into windows-hidden-title-and-menu

This commit is contained in:
Alex Andreev 2021-12-13 12:19:44 +03:00
commit 93ce2632a5
11 changed files with 118 additions and 28 deletions

View File

@ -6,6 +6,13 @@ Lens releases are built by CICD automatically on git tags. The typical release p
1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors. 1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors.
This can be helped (if you have the `gh` CLI installed) by running:
```
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "<VERSION>") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map(.merge_commit_sha) | join(" ")'
```
But you will probably need to verify that all the PRs have correct milestones.
1. From a clean and up to date `master` (or `release/vMAJOR.MINOR` if doing a patch release) run `npm version <version-type> --git-tag-version false` where `<version-type>` is one of the following: 1. From a clean and up to date `master` (or `release/vMAJOR.MINOR` if doing a patch release) run `npm version <version-type> --git-tag-version false` where `<version-type>` is one of the following:
- `major` - `major`
- `minor` - `minor`
@ -14,14 +21,27 @@ Lens releases are built by CICD automatically on git tags. The typical release p
- `preminor [--preid=<prerelease-id>]` - `preminor [--preid=<prerelease-id>]`
- `prepatch [--preid=<prerelease-id>]` - `prepatch [--preid=<prerelease-id>]`
- `prerelease [--preid=<prerelease-id>]` - `prerelease [--preid=<prerelease-id>]`
where `<prerelease-id>` is generally one of: where `<prerelease-id>` is generally one of:
- `alpha` - `alpha`
- `beta` - `beta`
- `rc` - `rc`
This assumes origin is set to https://github.com/lensapp/lens.git. If not then set GIT_REMOTE to the remote that is set to https://github.com/lensapp/lens.git. For example run `GIT_REMOTE=upstream npm version ...` This assumes origin is set to https://github.com/lensapp/lens.git. If not then set GIT_REMOTE to the remote that is set to https://github.com/lensapp/lens.git. For example run `GIT_REMOTE=upstream npm version ...`
1. Open the PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release. The PR description needs to be filled with the draft release description. From https://github.com/lensapp/lens click on Releases, the draft release should be first in the list, click `Edit` and copy/paste the markdown to the PR description. Add the `skip-changelog` label and click `Create Pull Request`. If this is a patch release be sure to set the PR base branch to `release/vMAJOR.MINOR` instead of `master`. 1. Open the PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release. The PR description needs to be filled with the draft release description. From https://github.com/lensapp/lens click on Releases, the draft release should be first in the list, click `Edit` and copy/paste the markdown to the PR description. Add the `skip-changelog` label and click `Create Pull Request`. If this is a patch release be sure to set the PR base branch to `release/vMAJOR.MINOR` instead of `master`.
It might also help, if the release drafter isn't updating correctly. To grab the data for the PR description using `gh` and `jq`. You can run the following command to get all the titles:
```
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "<VERSION>") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' | pbcopy
```
And if you want to specify just bug fixes then you can add the following to the end of the `[ ... ]` section in the above command (before the `sort_by`) to just have bug fixes. Switch to `== "enhancement"` for enhancements and `all()` with `. != "bug && . != "enhancement"` for maintanence sections.
```
| select(any(.labels | map(.name)[]; . == "bug"))
```
1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3. 1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3.
1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like: 1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like:
- `git push origin :refs/tags/vX.Y.Z-beta.N` (removes the tag from https://github.com/lensapp/lens) - `git push origin :refs/tags/vX.Y.Z-beta.N` (removes the tag from https://github.com/lensapp/lens)
@ -43,4 +63,4 @@ Other tasks
- generate a changelog from the prerelease descriptions (for major/minor releases) - generate a changelog from the prerelease descriptions (for major/minor releases)
- announce the release on lens and lens-hq slack channels (release is announced automatically on the community slack lens channel through the above publishing process) - announce the release on lens and lens-hq slack channels (release is announced automatically on the community slack lens channel through the above publishing process)
- announce on lens-hq that master is open for PR merges for the next release (for major/minor releases) - announce on lens-hq that master is open for PR merges for the next release (for major/minor releases)
- update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor/patch releases) - update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor/patch releases)

View File

@ -21,7 +21,6 @@
import { JsonApi, JsonApiData, JsonApiError } from "./json-api"; import { JsonApi, JsonApiData, JsonApiError } from "./json-api";
import type { Response } from "node-fetch"; import type { Response } from "node-fetch";
import type { Cluster } from "../../main/cluster";
import { LensProxy } from "../../main/lens-proxy"; import { LensProxy } from "../../main/lens-proxy";
import { apiKubePrefix, isDebugging } from "../vars"; import { apiKubePrefix, isDebugging } from "../vars";
@ -73,7 +72,7 @@ export interface KubeJsonApiError extends JsonApiError {
} }
export class KubeJsonApi extends JsonApi<KubeJsonApiData> { export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
static forCluster(cluster: Cluster): KubeJsonApi { static forCluster(clusterId: string): KubeJsonApi {
const port = LensProxy.getInstance().port; const port = LensProxy.getInstance().port;
return new this({ return new this({
@ -82,7 +81,7 @@ export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
debug: isDebugging, debug: isDebugging,
}, { }, {
headers: { headers: {
"Host": `${cluster.id}.localhost:${port}`, "Host": `${clusterId}.localhost:${port}`,
}, },
}); });
} }

View File

@ -55,6 +55,7 @@ export { ClusterRole, clusterRoleApi } from "../../common/k8s-api/endpoints";
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../common/k8s-api/endpoints"; export { ClusterRoleBinding, clusterRoleBindingApi } from "../../common/k8s-api/endpoints";
export { CustomResourceDefinition, crdApi } from "../../common/k8s-api/endpoints"; export { CustomResourceDefinition, crdApi } from "../../common/k8s-api/endpoints";
export { KubeObjectStatusLevel } from "./kube-object-status"; export { KubeObjectStatusLevel } from "./kube-object-status";
export { KubeJsonApi } from "../../common/k8s-api/kube-json-api";
// types // types
export type { ILocalKubeApiConfig, IRemoteKubeApiConfig, IKubeApiCluster } from "../../common/k8s-api/kube-api"; export type { ILocalKubeApiConfig, IRemoteKubeApiConfig, IKubeApiCluster } from "../../common/k8s-api/kube-api";

View File

@ -64,7 +64,7 @@ export class NodeShellSession extends ShellSession {
const args = ["exec", "-i", "-t", "-n", "kube-system", this.podName, "--"]; const args = ["exec", "-i", "-t", "-n", "kube-system", this.podName, "--"];
const nodeApi = new NodesApi({ const nodeApi = new NodesApi({
objectConstructor: Node, objectConstructor: Node,
request: KubeJsonApi.forCluster(this.cluster), request: KubeJsonApi.forCluster(this.cluster.id),
}); });
const node = await nodeApi.get({ name: this.nodeName }); const node = await nodeApi.get({ name: this.nodeName });
const nodeOs = node.getOperatingSystem(); const nodeOs = node.getOperatingSystem();

View File

@ -158,6 +158,8 @@ export abstract class ShellSession {
cwd, cwd,
env, env,
name: "xterm-256color", name: "xterm-256color",
// TODO: Something else is broken here so we need to force the use of winPty on windows
useConpty: false,
})); }));
} }

View File

@ -144,18 +144,6 @@ export class TerminalApi extends WebSocketApi<TerminalEvents> {
this.socket.binaryType = "arraybuffer"; this.socket.binaryType = "arraybuffer";
} }
destroy() {
if (!this.socket) return;
const controlCode = String.fromCharCode(4); // ctrl+d
this.sendMessage({ type: TerminalChannels.STDIN, data: controlCode });
setTimeout(() => super.destroy(), 2000);
}
reconnect() {
super.reconnect();
}
sendMessage(message: TerminalMessage) { sendMessage(message: TerminalMessage) {
return this.send(serialize(message)); return this.send(serialize(message));
} }

View File

@ -153,7 +153,7 @@ export class ClusterStatus extends React.Component<Props> {
return ( return (
<div className={cssNames(styles.status, "flex column box center align-center justify-center", this.props.className)}> <div className={cssNames(styles.status, "flex column box center align-center justify-center", this.props.className)}>
<div className="flex items-center column gaps"> <div className="flex items-center column gaps">
<h2>{this.entity.getName()}</h2> <h2>{this.entity?.getName() ?? this.cluster.name}</h2>
{this.renderStatusIcon()} {this.renderStatusIcon()}
{this.renderAuthenticationOutput()} {this.renderAuthenticationOutput()}
{this.renderReconnectionHelp()} {this.renderReconnectionHelp()}

View File

@ -57,18 +57,25 @@ describe("<SidebarCluster/>", () => {
}); });
it("renders cluster avatar and name", () => { it("renders cluster avatar and name", () => {
const { getByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>); const { getByText, getAllByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
expect(getByText("tc")).toBeInTheDocument(); expect(getByText("tc")).toBeInTheDocument();
expect(getByText("test-cluster")).toBeInTheDocument();
const v = getAllByText("test-cluster");
expect(v.length).toBeGreaterThan(0);
for (const e of v) {
expect(e).toBeInTheDocument();
}
}); });
it("renders cluster menu", async () => { it("renders cluster menu", () => {
const { getByTestId, getByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>); const { getByTestId, getByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
const link = getByTestId("sidebar-cluster-dropdown"); const link = getByTestId("sidebar-cluster-dropdown");
fireEvent.click(link); fireEvent.click(link);
expect(await getByText("Add to Hotbar")).toBeInTheDocument(); expect(getByText("Add to Hotbar")).toBeInTheDocument();
}); });
}); });

View File

@ -26,6 +26,7 @@ import { observer } from "mobx-react";
import { cssNames, IClassName } from "../../utils"; import { cssNames, IClassName } from "../../utils";
import { navigation } from "../../navigation"; import { navigation } from "../../navigation";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { catalogURL } from "../../../common/routes";
export interface SettingLayoutProps extends React.DOMAttributes<any> { export interface SettingLayoutProps extends React.DOMAttributes<any> {
className?: IClassName; className?: IClassName;
@ -39,7 +40,13 @@ export interface SettingLayoutProps extends React.DOMAttributes<any> {
const defaultProps: Partial<SettingLayoutProps> = { const defaultProps: Partial<SettingLayoutProps> = {
provideBackButtonNavigation: true, provideBackButtonNavigation: true,
contentGaps: true, contentGaps: true,
back: () => navigation.goBack(), back: () => {
if (navigation.length <= 1) {
navigation.push(catalogURL());
} else {
navigation.goBack();
}
},
}; };
/** /**

View File

@ -61,4 +61,51 @@
.avatar { .avatar {
font-weight: 500; font-weight: 500;
margin-right: 1.25rem; margin-right: 1.25rem;
} }
.loadingAvatar {
position: relative;
pointer-events: none;
&:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 0;
height: 100%;
background: transparentize(white, .85);
animation: waiting 1.5s infinite linear;
}
}
.loadingClusterName {
position: relative;
pointer-events: none;
width: 80%;
height: 16px;
&:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 0;
height: 100%;
background: transparentize(white, .85);
animation: waiting 1.5s infinite linear;
}
}
@keyframes waiting {
0% {
left: 0;
width: 0;
}
50% {
left: 25%;
width: 75%;
}
75% {
left: 100%;
width: 0;
}
}

View File

@ -31,6 +31,7 @@ import { Icon } from "../icon";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import { Menu, MenuItem } from "../menu"; import { Menu, MenuItem } from "../menu";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { Tooltip } from "../tooltip";
const contextMenu: CatalogEntityContextMenuContext = observable({ const contextMenu: CatalogEntityContextMenuContext = observable({
menuItems: [], menuItems: [],
@ -60,11 +61,25 @@ function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
} }
} }
function renderLoadingSidebarCluster() {
return (
<div className={styles.SidebarCluster}>
<Avatar
title="??"
background="var(--halfGray)"
size={40}
className={styles.loadingAvatar}
/>
<div className={styles.loadingClusterName} />
</div>
);
}
export function SidebarCluster({ clusterEntity }: { clusterEntity: CatalogEntity }) { export function SidebarCluster({ clusterEntity }: { clusterEntity: CatalogEntity }) {
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
if (!clusterEntity) { if (!clusterEntity) {
return null; return renderLoadingSidebarCluster();
} }
const onMenuOpen = () => { const onMenuOpen = () => {
@ -95,6 +110,7 @@ export function SidebarCluster({ clusterEntity }: { clusterEntity: CatalogEntity
const { metadata, spec } = clusterEntity; const { metadata, spec } = clusterEntity;
const id = `cluster-${metadata.uid}`; const id = `cluster-${metadata.uid}`;
const tooltipId = `tooltip-${id}`;
return ( return (
<div <div
@ -112,9 +128,12 @@ export function SidebarCluster({ clusterEntity }: { clusterEntity: CatalogEntity
src={spec.icon?.src} src={spec.icon?.src}
className={styles.avatar} className={styles.avatar}
/> />
<div className={styles.clusterName}> <div className={styles.clusterName} id={tooltipId}>
{metadata.name} {metadata.name}
</div> </div>
<Tooltip targetId={tooltipId}>
{metadata.name}
</Tooltip>
<Icon material="arrow_drop_down" className={styles.dropdown}/> <Icon material="arrow_drop_down" className={styles.dropdown}/>
<Menu <Menu
usePortal usePortal