Skip to content

Commit

Permalink
release 1.0.22 with frontend features
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenchristopher-zip committed Sep 26, 2022
1 parent 4d8d4cd commit d63f9cb
Show file tree
Hide file tree
Showing 12 changed files with 608 additions and 130 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ spinnakerBundle {
pluginId = "zip.deployboard"
description = "Customization for Zip deploys"
provider = "https://github.com/Greenbax/zip-spinnaker-deployboard"
version = "1.0.21"
version = "1.0.22"
}

subprojects {
Expand Down
8 changes: 4 additions & 4 deletions plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
"provider": "https://github.com/Greenbax/zip-spinnaker-deployboard",
"releases": [
{
"version": "1.0.21",
"date": "2022-09-26T02:00:34.329914Z",
"version": "1.0.22",
"date": "2022-09-26T21:49:32.121432Z",
"requires": "gate>=0.0.0,orca>=0.0.0,deck>=0.0.0",
"sha512sum": "db68103290d50078fecd0583d83b6afc8c05b30878f32c9b1cc272e2309905a8870d952f8874f63dd376e3f0bb50fb64d351093531a48a6ddd618bfb2b5a049a",
"sha512sum": "8a0d5264e3ff659b3ce08da8f319d27126cb244fbf5b52f9eb6ad38db948b3626020274d0fa5ab3e8443b9c6f4fcfd707c70583c0cfba86aad66af9b95452c49",
"state": "",
"url": "https://github.com/Greenbax/zip-spinnaker-deployboard/releases/download/v1.0.21/zip-spinnaker-deployboard.zip"
"url": "https://github.com/Greenbax/zip-spinnaker-deployboard/releases/download/v1.0.22/zip-spinnaker-deployboard.zip"
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions zip-deployboard-deck/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
build
4 changes: 4 additions & 0 deletions zip-deployboard-deck/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"watch": "rollup -c -w --no-watch.clearScreen"
},
"dependencies": {
"@material-ui/core": "4.12.4",
"@material-ui/icons": "^4.11.3",
"@rollup/plugin-commonjs": "18.0.0",
"@rollup/plugin-typescript": "8.2.1",
"@rollup/plugin-url": "6.0.0",
Expand All @@ -31,6 +33,8 @@
"react": "16.14.0",
"react-bootstrap": "^2.5.0",
"react-dom": "16.14.0",
"react-hot-toast": "^2.4.0",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^6.3.0",
"rollup": "2.45.2",
"rxjs": "6.6.7",
Expand Down
1 change: 0 additions & 1 deletion zip-deployboard-deck/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Snapshots, SnapshotsDataSource } from './snapshots';
export const plugin: IDeckPlugin = {
stages: [dynamoStatusStage],
initialize: () => {
console.log('initializing');
const injector = (window as any).spinnaker.$injector;
const applicationState: ApplicationStateProvider = injector.get('applicationState');
applicationState.addChildState({
Expand Down
7 changes: 7 additions & 0 deletions zip-deployboard-deck/src/snapshots/Snapshots.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.snapshot-modal {
opacity: 1;
top: 25%;
transform: translateY(-50%);
display: flex !important;
align-items: center;
}
194 changes: 175 additions & 19 deletions zip-deployboard-deck/src/snapshots/Snapshots.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,190 @@
import { useCurrentStateAndParams, useRouter } from '@uirouter/react';
import React, { useState } from 'react';
import { ArrowDropDown } from '@material-ui/icons';
import { cloneDeep } from 'lodash';
import React, { useEffect, useState } from 'react';
import { Button, Dropdown, Modal, ModalBody } from 'react-bootstrap';
import toast, { LoaderIcon } from 'react-hot-toast';
import InfiniteScroll from 'react-infinite-scroll-component';

import type { Application } from '@spinnaker/core';
import { useDataSource } from '@spinnaker/core';
import type { Application, IPipelineCommand } from '@spinnaker/core';
import { ModalClose, ReactInjector, TextInput, useData } from '@spinnaker/core';

import type { SnapshotType } from './SnapshotsDataSource';
import { SnapshotsReader } from './SnapshotsDataSource';
import { SnapshotsTable } from './SnapshotsTable';
import { SNAPSHOT_CONFIGS } from './snapshot.config';

import './Snapshots.less';

interface SnapshotsProps {
app: Application;
}

const getBuildsWrapped = (branch: string, query?: string, lastSortKeySeen?: string) => () =>
SnapshotsReader.getBuilds(branch, query, lastSortKeySeen);

export function Snapshots(props: SnapshotsProps) {
const dataSource = props.app.getDataSource('snapshots');
const { data, status, loaded } = useDataSource<SnapshotType[]>(dataSource);
React.useEffect(() => {
dataSource.activate();
}, []);
const [query, setQuery] = useState('');
const [branch, setBranch] = useState('prod');
const [lastSortKeySeen, setLastSortKeySeen] = useState('');
const [deployBuild, setDeployBuild] = useState('');
const [deployPipeline, setDeployPipeline] = useState('prod');
const [expandedBuilds, setExpandedBuilds] = useState(new Set<number>());

const { result, status } = useData<SnapshotType[]>(
getBuildsWrapped(branch, query || undefined, lastSortKeySeen || undefined),
[],
[query, branch, lastSortKeySeen],
);
const [builds, setBuilds] = useState<SnapshotType[]>([]);
useEffect(() => {
setBuilds(dedupeBuilds(builds.concat(result)));
}, [result]);

// Use the wrapped state setters so we can clear branch data cache.
const setBranchWrapped = (branch: string) => {
setBuilds([]);
setLastSortKeySeen('');
setBranch(branch);
};
const setQueryWrapped = (query: string) => {
setBuilds([]);
setLastSortKeySeen('');
setQuery(query);
};

const expandBuild = (buildNumber: number) => setExpandedBuilds(new Set<number>(expandedBuilds.add(buildNumber)));
const collapseBuild = (buildNumber: number) => {
const copy = new Set<number>(expandedBuilds);
copy.delete(buildNumber);
setExpandedBuilds(copy);
};

const router = useRouter();
const { params, state } = useCurrentStateAndParams();
const [deployCommit, setDeployCommit] = useState('');
const [currentSort, setCurrentSort] = useState('Timestamp');
const dedupeBuilds = (builds: SnapshotType[]): SnapshotType[] => {
const dedupedBuilds = builds.reduce((acc, val) => acc.set(val.buildNumber, val), new Map());
return Array.from(dedupedBuilds.values()).sort((a, b) => b.buildNumber - a.buildNumber);
};

const startPipeline = (command: IPipelineCommand): PromiseLike<void> => {
const { executionService } = ReactInjector;
const buildsCopy = cloneDeep(builds);
buildsCopy.find((val) => val.dockerImage === deployBuild).status = 'DEPLOYING';
setBuilds(buildsCopy);
setDeployBuild('');
return executionService
.startAndMonitorPipeline(props.app, command.pipelineName, command.trigger)
.then((monitor: any) => {
toast.success(`Pipeline '${command.pipelineName}' started!`);
return monitor.promise;
});
};

return (
<SnapshotsTable
commits={data}
toggleDeploy={setDeployCommit}
currentSort={currentSort}
toggleSort={setCurrentSort}
/>
<>
<SnapshotDeployModal
pipeline={SNAPSHOT_CONFIGS.get(deployPipeline).pipeline}
setPipeline={setDeployPipeline}
image={deployBuild}
onClose={() => setDeployBuild('')}
onSubmit={startPipeline}
/>
<div style={{ width: '95%' }}>
<InfiniteScroll
hasMore={builds.length % 25 === 0}
dataLength={builds.length}
loader={<LoaderIcon />}
height={'95vh'}
next={() => {
if (builds.length === 0) {
return;
}
const lastBuild = builds[builds.length - 1];
const lastCommit = lastBuild.commits[lastBuild.commits.length - 1];
setLastSortKeySeen(`${lastBuild.buildNumber}#${lastCommit.ts}#${lastCommit.author}#${lastCommit.sha}`);
}}
>
<div style={{ display: 'flex', marginBottom: '20px' }}>
<TextInput
autoFocus
onChange={(val) => setQueryWrapped(val.target.value)}
value={query}
placeholder="Author or commit message (note: search is case sensitive)..."
/>
<div style={{ marginLeft: '20px' }}>
<Dropdown>
<Dropdown.Toggle>
<span style={{ display: 'flex', alignItems: 'center' }}>
{`Branch: ${branch}`}
<ArrowDropDown />
</span>
</Dropdown.Toggle>
<Dropdown.Menu>
{Array.from(SNAPSHOT_CONFIGS.values()).map((config) => (
<li>
<a onClick={() => setBranchWrapped(config.gitBranch)}>{config.label}</a>
</li>
))}
</Dropdown.Menu>
</Dropdown>
</div>
</div>
{status === 'RESOLVED' || builds.length ? (
<SnapshotsTable
builds={builds}
expandedBuilds={expandedBuilds}
expandBuild={expandBuild}
collapseBuild={collapseBuild}
toggleDeploy={setDeployBuild}
/>
) : (
<LoaderIcon />
)}
</InfiniteScroll>
</div>
</>
);
}

interface SnapshotDeployModalProps {
pipeline: string;
setPipeline: (pipeline: string) => void;
image: string;
onClose: () => void;
onSubmit: (command: IPipelineCommand) => PromiseLike<void>;
}

const SnapshotDeployModal = ({ pipeline, setPipeline, image, onClose, onSubmit }: SnapshotDeployModalProps) => {
const command: IPipelineCommand = {
pipelineName: pipeline,
parameters: { image },
trigger: { enabled: true, type: 'manual', parameters: { image } },
};
return (
<Modal show={Boolean(image)} onHide={onClose} className="snapshot-modal">
<ModalClose dismiss={onClose} />
<Modal.Header>
<Modal.Title>Deploy build</Modal.Title>
</Modal.Header>
<ModalBody>
<label>Select a pipeline</label>
<Dropdown>
<Dropdown.Toggle>
<span style={{ display: 'flex', alignItems: 'center' }}>
{`Pipeline: ${pipeline}`}
<ArrowDropDown />
</span>
</Dropdown.Toggle>
<Dropdown.Menu>
{Array.from(SNAPSHOT_CONFIGS.values()).map((config) => (
<li>
<a onClick={() => setPipeline(config.gitBranch)}>{config.label}</a>
</li>
))}
</Dropdown.Menu>
</Dropdown>
</ModalBody>
<Modal.Footer>
<Button onClick={() => onSubmit(command)}>Deploy</Button>
</Modal.Footer>
</Modal>
);
};
88 changes: 52 additions & 36 deletions zip-deployboard-deck/src/snapshots/SnapshotsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@ import type { Application, IDataSourceConfig } from '@spinnaker/core';
import { REST } from '@spinnaker/core';

export interface SnapshotType {
branch: string;
buildNumber: number;
dockerImage: string;
status: string;
commits: CommitType[];
}

export interface CommitType {
author: string;
sha: string;
message: string;
author: string;
timestamp: string;
branch: string;
currentlyDeployed?: string;
ts: string;
}

interface SnapshotAPI {
builds: Array<{
branch: string;
buildNumber: number;
dockerImage: string;
status: string;
commits: Array<{
author: string;
sha: string;
message: string;
ts: string;
}>;
}>;
}

export const SnapshotsDataSource: IDataSourceConfig<SnapshotType[]> = {
Expand All @@ -17,51 +38,46 @@ export const SnapshotsDataSource: IDataSourceConfig<SnapshotType[]> = {
activeState: '**.snapshots.**',
visible: true,
sref: '.snapshots',
defaultData: [
{
author: 'test',
sha: 'test',
message: 'test',
timestamp: 'test',
branch: 'test',
currentlyDeployed: 'test',
},
{
author: 'test',
sha: 'test',
message: 'test',
timestamp: 'test',
branch: 'test',
currentlyDeployed: 'test',
},
{
author: 'test',
sha: 'test',
message: 'test',
timestamp: 'test',
branch: 'test',
currentlyDeployed: 'test',
},
],
defaultData: [],
description: 'Snapshot View',
iconName: 'build',
loader: (application: Application) => SnapshotsReader.getBuilds(),
loader: (application: Application) => SnapshotsReader.getBuilds('prod'),
onLoad: (application: Application, data: any) => Promise.resolve(data),
lazy: true,
};

const transformSnapshot = (build) => {
return;
const transformSnapshot = (resp: SnapshotAPI): SnapshotType[] => {
return resp.builds.map((val) => ({
branch: val.branch,
buildNumber: val.buildNumber,
dockerImage: val.dockerImage,
status: val.status,
commits: transformCommits(val.commits),
}));
};

const transformCommits = (commits: SnapshotAPI['builds'][0]['commits']): CommitType[] => {
return commits.map((val): CommitType => ({ author: val.author, sha: val.sha, message: val.message, ts: val.ts }));
};

export class SnapshotsReader {
public static get MAX_LINES(): number {
return 4095;
}

public static getBuilds(): PromiseLike<SnapshotType[]> {
return REST('snapshots/builds')
public static getBuilds(branch: string, query?: string, lastSortKeySeen?: string): PromiseLike<SnapshotType[]> {
const searchQuery: any = { branch };
if (query) {
searchQuery['query'] = query;
}
if (lastSortKeySeen) {
searchQuery['lastSortKeySeen'] = lastSortKeySeen;
}
return REST('/extensions/snapshots')
.query(searchQuery)
.get()
.then((builds) => builds.map((build: any) => transformSnapshot(build)));
.then((builds) => {
return transformSnapshot(builds);
});
}
}
Loading

0 comments on commit d63f9cb

Please sign in to comment.