Skip to content

Commit

Permalink
Custom patches to deck (v3.14.2) in spinnaker 1.31.3 release
Browse files Browse the repository at this point in the history
Cherry-picked from 3.13.3:
3.13.3-20240203-patch-4: use native pipeline index for sorting in UI
3.13.3-20240131-patch-2: add copy to clipboard to images in the execution artifacts tab
3.13.3-20240131-patch-1: add copy to clipboard to images in the cluster view
3.13.3-20240131-patch-1: add copy to clipboard to artifacts shown in pod information pane
3.13.3-20240131-patch-1: add copy to clipboard to artifacts shown in deployment information pane
3.13.3-20240131-patch-1: add copy to clipboard to artifacts in pipeline status
3.12.3-20240130-patch-2: beautify how the new artifact item looks
3.12.3-20240130-patch-1: apply our patches to show docker image name in the pipeline status suitable for copy/pasting
3.12.3-20230912-patch-1: apply our patches to show services dns names in UI
  • Loading branch information
igcherkaev committed Oct 24, 2024
1 parent 9c850c3 commit ab57406
Show file tree
Hide file tree
Showing 18 changed files with 165 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ test/functional/cypress/screenshots/**/*

# vite local files
**/.env.local
settings-my.js
6 changes: 3 additions & 3 deletions Dockerfile.slim
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ FROM debian:stable-slim
LABEL maintainer="[email protected]"

WORKDIR /opt/deck
COPY docker /opt/deck/docker
RUN chown www-data:www-data .
COPY --chown=www-data:www-data docker /opt/deck/docker
RUN docker/setup-apache2.sh

COPY build/webpack /opt/deck/html
RUN chown -R www-data:www-data /opt/deck
COPY --chown=www-data:www-data build/webpack /opt/deck/html
USER www-data

CMD docker/run-apache2.sh
6 changes: 6 additions & 0 deletions packages/core/src/artifact/artifactTab.less
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
}
}

.artifact-list-row {
display: flex;
flex-direction: row;
align-items: baseline;
}

.artifact-list-item {
height: 28px;
position: relative;
Expand Down
26 changes: 19 additions & 7 deletions packages/core/src/artifact/react/ArtifactIconList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,45 @@ import React from 'react';
import { ArtifactIconService } from '../ArtifactIconService';
import { ArtifactTypePatterns } from '../ArtifactTypes';
import type { IArtifact } from '../../domain';
import { CopyToClipboard } from '../../utils';

export interface IArtifactIconListProps {
artifacts: IArtifact[];
}

function artifactDelimiter(artifact: IArtifact): string {
switch (artifact.type) {
case 'docker/image':
return ':';
default:
return ' - ';
}
}
export const ArtifactIconList = (props: IArtifactIconListProps): any => {
return props.artifacts.map((artifact, i) => {
const { location, reference, type } = artifact;
let { name } = artifact;
const iconPath = ArtifactIconService.getPath(type);
const key = `${location || ''}${type || ''}${reference || ''}` || String(i);
let title = type;
let artifactName = `${name || reference}${artifact.version ? artifactDelimiter(artifact) + artifact.version : ''}`;
let title = artifactName;
const copyToClipboardText = artifactDelimiter(artifact) === ':' ? artifactName : '';
if (isString(type) && type.length > 0) {
const k8sKindMatch = type.match(ArtifactTypePatterns.KUBERNETES);
if (k8sKindMatch != null && k8sKindMatch[1].length > 0) {
const kind = k8sKindMatch[1].substr(0, 1).toUpperCase() + k8sKindMatch[1].substr(1);
name = `${kind} ${name}`;
title = name;
artifactName = `${name || reference}${artifact.version ? artifactDelimiter(artifact) + artifact.version : ''}`;
}
}
return (
<div key={key} className="artifact-list-item" title={title}>
{iconPath && <img className="artifact-list-item-icon" width="20" height="20" src={iconPath} />}
<span className="artifact-list-item-name">
{name}
{artifact.version && <span> - {artifact.version}</span>}
</span>
<div className="artifact-list-row">
<div key={key} className="artifact-list-item" title={title}>
{iconPath && <img className="artifact-list-item-icon" width="20" height="20" src={iconPath} />}
<span className="artifact-list-item-name">{artifactName}</span>
</div>
{copyToClipboardText && <CopyToClipboard text={copyToClipboardText} toolTip="Copy to clipboard" />}
</div>
);
});
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/cluster/rollups.less
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@ running-tasks-tag {
margin-left: 3px;
}

.padded-images-span {
padding-left: 35px;
}

.server-group-title,
.subgroup-title {
padding: 5px 10px 2px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get, isEmpty, set } from 'lodash';
import { get } from 'lodash';
import { $log } from 'ngimport';
import React from 'react';
import { Modal } from 'react-bootstrap';
Expand Down Expand Up @@ -27,22 +27,13 @@ export function DeletePipelineModal(props: IDeletePipelineModalProps) {

PipelineConfigService.deletePipeline(application.name, pipeline, pipeline.name).then(
() => {
const idsToUpdatedIndices = {};
// const idsToUpdatedIndices = {};
const isPipelineStrategy = pipeline.strategy === true;
const data = isPipelineStrategy ? application.strategyConfigs.data : application.pipelineConfigs.data;
data.splice(
data.findIndex((p: any) => p.id === pipeline.id),
1,
);
data.forEach((p: IPipeline, index: number) => {
if (p.index !== index) {
p.index = index;
set(idsToUpdatedIndices, p.id, index);
}
});
if (!isEmpty(idsToUpdatedIndices)) {
PipelineConfigService.reorderPipelines(application.name, idsToUpdatedIndices, isPipelineStrategy);
}
ReactInjector.$state.go('^.executions', null, { location: 'replace' });
closeModal();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ export class PipelineConfigService {
}

const endpoint = pipeline.strategy ? 'strategies' : 'pipelines';
return REST(endpoint).query({ staleCheck: true }).post(pipeline);
// return REST(endpoint).query({ staleCheck: true }).post(pipeline);
// temp turn off stale check as it causes gate to throw 400 for unknown reason
return REST(endpoint).post(pipeline);
}

public static reorderPipelines(
Expand Down Expand Up @@ -170,22 +172,8 @@ export class PipelineConfigService {
return uniq(upstreamStages);
}

private static sortPipelines(pipelines: IPipeline[]): PromiseLike<IPipeline[]> {
static sortPipelines(pipelines: IPipeline[]): PromiseLike<IPipeline[]> {
const sorted = sortBy(pipelines, ['index', 'name']);

// if there are pipelines with a bad index, fix that
const toReindex: Array<PromiseLike<void>> = [];
if (sorted && sorted.length) {
sorted.forEach((pipeline, index) => {
if (pipeline.index !== index) {
pipeline.index = index;
toReindex.push(this.savePipeline(pipeline));
}
});
if (toReindex.length) {
return $q.all(toReindex).then(() => sorted);
}
}
return $q.resolve(sorted);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class PipelineConfigValidator {
if (pipeline.strategy && !pipeline.stages.some((stage) => stage.type === 'deploy')) {
messages.push('To be able to create new server groups, a custom strategy should contain a Deploy stage.');
}
if ((pipeline.expectedArtifacts || []).some((a) => !a.matchArtifact || (a.matchArtifact as any) === {})) {
if ((pipeline.expectedArtifacts || []).some((a) => !a.matchArtifact || (a.matchArtifact as any) == {})) {
messages.push('Every expected artifact must specify an artifact to match against.');
}
return messages;
Expand Down
56 changes: 53 additions & 3 deletions packages/core/src/pipeline/filter/ExecutionFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,17 +208,67 @@ export class ExecutionFilters extends React.Component<IExecutionFiltersProps, IE
return PipelineConfigService.reorderPipelines(this.props.application.name, idsToUpdatedIndices, false);
}

private setPipelineIndex(pipelineId: string, index: number) {
const { application } = this.props;
for (let i = 0; i < application.pipelineConfigs.data.length; i++) {
if (application.pipelineConfigs.data[i].id === pipelineId) {
application.pipelineConfigs.data[i].index = index;
break;
}
}
}

private getPipelineByName(name: string): IPipeline | null {
const { application } = this.props;
for (const p of application.pipelineConfigs.data) {
if (p.name === name) {
return p;
}
}
return null;
}

// For ReactSortable
private handleSortEnd = (sortEnd: SortEnd): void => {
if (sortEnd.oldIndex === sortEnd.newIndex) {
return;
}
const pipelineNames = arrayMove(this.state.pipelineNames, sortEnd.oldIndex, sortEnd.newIndex);
this.applyNewPipelineSortOrder(pipelineNames);
this.setState({ pipelineNames: pipelineNames });
this.sortAsVisuallySeen(pipelineNames);
};

private sortAlphabetically = () => {
const pipelineNames = this.state.pipelineNames.slice().sort();
this.applyNewPipelineSortOrder(pipelineNames);
this.setState({ pipelineNames: pipelineNames });
this.sortAsVisuallySeen(pipelineNames);
};

private sortAsVisuallySeen(pipelineNames: string[]) {
const idsToUpdatedIndices: { [key: string]: number } = {};
let i = 0;
const indices: number[] = [];
this.props.application.pipelineConfigs.data.forEach((pipeline: IPipeline) => {
indices.push(pipeline.index);
});
for (const pName of pipelineNames) {
const pipeline = this.getPipelineByName(pName);
const toIndex = indices[i];
if (pipeline.index !== toIndex) {
idsToUpdatedIndices[pipeline.id] = toIndex;
this.setPipelineIndex(pipeline.id, toIndex);
}
i = i + 1;
}
if (!isEmpty(idsToUpdatedIndices)) {
this.updatePipelines(idsToUpdatedIndices).then(() => {
this.refreshPipelines();
});
}
}

// @ts-ignore
// noinspection JSUnusedLocalSymbols
private applyNewPipelineSortOrder = (pipelineNames: string[]): void => {
const { application } = this.props;
logger.log({ category: 'Pipelines', action: 'Reordered pipeline' });
Expand All @@ -232,7 +282,7 @@ export class ExecutionFilters extends React.Component<IExecutionFiltersProps, IE
});

if (!isEmpty(idsToUpdatedIndices)) {
this.updatePipelines(idsToUpdatedIndices);
this.updatePipelines(idsToUpdatedIndices).then(() => {});
this.refreshPipelines();
}
};
Expand Down
26 changes: 23 additions & 3 deletions packages/core/src/pipeline/status/Artifact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';

import { ArtifactIconService } from '../../artifact';
import type { IArtifact } from '../../domain';
import { CopyToClipboard } from '../../utils';

import './artifact.less';

Expand Down Expand Up @@ -32,24 +33,43 @@ export class Artifact extends React.Component<IArtifactProps> {
return tooltipEntries.join('\n');
}

private artifactDelimiter(artifact: IArtifact): string {
switch (artifact.type) {
case 'docker/image':
return ':';
default:
return ' - ';
}
}

public render() {
const { artifact, isDefault } = this.props;
const { name, reference, version, type } = artifact;
const artifactName = `${name || reference}${this.artifactDelimiter(artifact)}${version || 'latest'}`;
const copyToClipboardText = this.artifactDelimiter(artifact) === ':' ? artifactName : '';

return (
<div className="artifact-details">
<dl title={this.tooltip(artifact, isDefault)}>
<div className="artifact-detail">
<dt>
{ArtifactIconService.getPath(type) ? (
<img className="artifact-icon" src={ArtifactIconService.getPath(type)} width="18" height="18" />
<img
className="artifact-icon"
alt={type}
src={ArtifactIconService.getPath(type)}
width="36"
height="36"
/>
) : (
<span>[{type}] </span>
)}
</dt>
<dd>
<div className="artifact-name">{name || reference}</div>
{version && <div className="artifact-version"> - {version}</div>}
<pre>
{artifactName}
{copyToClipboardText && <CopyToClipboard text={copyToClipboardText} toolTip="Copy to clipboard" />}
</pre>
</dd>
</div>
</dl>
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/pipeline/status/artifact.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
.artifact-detail {
display: inline-flex;
padding: 0.1px;
align-items: baseline;

.artifact-icon {
padding-right: 4px;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/presentation/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,8 @@ ul.checkmap {

.break-word {
overflow-wrap: break-word;
text-wrap: avoid;
white-space: nowrap;
}

.horizontal-rule {
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/serverGroup/ServerGroupHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LoadBalancersTagWrapper } from '../loadBalancer';
import { NameUtils } from '../naming';
import { Overridable } from '../overrideRegistry';
import { RunningTasksTag } from './pod/RunningTasksTag';
import { CopyToClipboard } from '../utils';

export interface IServerGroupHeaderProps {
application: Application;
Expand Down Expand Up @@ -83,7 +84,10 @@ export class ImageList extends React.Component<IServerGroupHeaderProps, IImageLi
<>
{collapsed && (
<>
<span>{images[0]}</span>
<span>
{images[0]}
<CopyToClipboard text={images[0]} toolTip="Copy to clipboard"></CopyToClipboard>
</span>
&nbsp;
{images.length > 1 && (
<button className="link" onClick={this.toggle} style={buttonStyle}>
Expand All @@ -96,9 +100,10 @@ export class ImageList extends React.Component<IServerGroupHeaderProps, IImageLi
<>
{images.map((image, index) => (
<span key={image}>
{index > 0 && <br />}
{index > 0 && <span className="padded-images-span"> </span>}
{image}
{index < images.length - 1 ? ',' : ''}
<CopyToClipboard text={image} toolTip="Copy to clipboard"></CopyToClipboard>
{index < images.length - 1 ? <br /> : ''}
</span>
))}
<br />
Expand Down
11 changes: 11 additions & 0 deletions packages/kubernetes/src/instance/details/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@
<kubernetes-manifest-labels manifest="ctrl.manifest.manifest"></kubernetes-manifest-labels>
</collapsible-section>

<collapsible-section heading="Images" expanded="true">
<ul ng-repeat="artifact in ctrl.manifest.artifacts">
<kubernetes-manifest-artifact artifact="artifact"></kubernetes-manifest-artifact>
<copy-to-clipboard
class="copy-to-clipboard copy-to-clipboard-sm"
text="artifact.reference"
tool-tip="'Copy to clipboard'"
></copy-to-clipboard>
</ul>
</collapsible-section>

<instance-links
address="ctrl.instance.publicDnsName"
application="ctrl.app"
Expand Down
Loading

0 comments on commit ab57406

Please sign in to comment.