From 889bb69608d24d7dbd96ec1e7855b925014cfa80 Mon Sep 17 00:00:00 2001
From: Sergey Smolnikov
Date: Tue, 13 Apr 2021 14:58:09 +0200
Subject: [PATCH] 83862 jobs in web console (#276)
* Labels for jobs renamed to pipeline jobs
* Labels for jobs renamed to pipeline jobs
* Added type to component-summary
* Split list to components and jobs in job Artefacts
* Split list to components and jobs in environment overview
* Split list to components and jobs in environment overview
* Cleanup
* Added job logs
* Added job logs
* Added job list to component summary
* Fixed unit-tests
* Fixed scheduled job form
* Added time and durations
* Extracted common logic from active component overview
* Cleanup and fixes
* Cleanup and fixes
* Cleanup and fixes
* Shown scheduler port and payload
* logs used env
* extracted common logic
* fixed layouts
* fixed layouts
* fixed names and tests
---
proxy/server.dev.conf | 2 +-
src/components/app-navbar/index.js | 2 +-
src/components/app-overview/index.js | 4 +-
.../component/active-component-secrets.js | 63 +++++
.../component/component-bread-crumb.js | 34 +++
src/components/component/component-ports.js | 29 ++
src/components/component/component-secrets.js | 39 +++
src/components/component/env-variables.js | 56 ++++
.../component/job-scheduler-details.js | 50 ++++
.../configure-application-github/index.js | 2 +-
src/components/events-list/index.test.js | 2 +-
src/components/job-overview/component-list.js | 26 ++
src/components/job-overview/index.js | 17 +-
src/components/jobs-list/index.js | 2 +-
.../active-component-overview.js | 257 +++---------------
.../page-active-component/default-alias.js | 36 +++
.../horizontal-scaling-summary.js | 44 +++
.../page-active-component/overview.js | 63 +++++
.../page-active-component/replica-list.js | 54 ++++
.../active-job-component-overview.js | 114 ++++++++
.../page-active-job-component/index.js | 39 +++
.../page-active-job-component/overview.js | 24 ++
.../scheduled-job-list.js | 51 ++++
src/components/page-application/index.js | 6 +-
.../page-component/component-overview.js | 166 -----------
.../change-config-branch-form.js | 2 +-
.../deployment-component-overview.js | 106 ++++++++
.../index.js | 16 +-
.../deployment-job-component-overview.js | 111 ++++++++
.../page-deployment-job-component/index.js | 27 ++
.../page-deployment/deployment-bread-crumb.js | 27 ++
.../deployment-component-bread-crumb.js | 41 +++
.../deployment-component-list.js | 44 +++
.../deployment-job-component-list.js | 48 ++++
.../page-deployment/deployment-overview.js | 147 +++-------
.../page-deployment/deployment-summary.js | 67 +++++
src/components/page-deployment/index.js | 10 +-
.../promote-deployment-action.js | 42 +++
.../page-environment/component-list-item.js | 51 ++++
.../page-environment/component-list.js | 33 +++
.../page-environment/environment-overview.js | 30 +-
src/components/page-environment/index.js | 5 +
.../index.js | 27 +-
.../{page-job => page-pipeline-job}/index.js | 6 +-
.../index.js | 12 +-
.../style.css | 0
src/components/page-replica/index.js | 51 +++-
src/components/page-replica/use-poll-logs.js | 6 +-
.../page-replica/use-select-replica.js | 3 +-
src/components/page-scheduled-job/index.js | 143 ++++++++++
.../page-scheduled-job/use-poll-logs.js | 13 +
.../use-select-scheduled-job.js | 31 +++
src/components/page-step/index.js | 5 +-
src/components/scheduled-job-status/index.js | 21 ++
src/components/time/duration.js | 4 +-
src/models/component-summary/index.js | 2 +
src/models/component-summary/test-data.js | 2 +
src/models/component-type/index.js | 28 ++
src/models/component-type/normaliser.js | 0
src/models/component/index.js | 8 +
src/models/component/normaliser.js | 5 +
src/models/job-summary/normaliser.js | 2 +-
src/models/job/test-data.js | 2 +
src/models/replica-summary/index.js | 2 +
src/models/replica-summary/normaliser.js | 6 +
src/models/replica-summary/test-data.js | 4 +
src/models/scheduled-job-summary/index.js | 12 +
.../scheduled-job-summary/normaliser.js | 18 ++
src/models/scheduled-job-summary/test-data.js | 46 ++++
src/routes.js | 3 +
src/state/environment/index.js | 8 +-
src/utils/routing.js | 22 +-
src/utils/string.js | 2 +
73 files changed, 1884 insertions(+), 599 deletions(-)
create mode 100644 src/components/component/active-component-secrets.js
create mode 100644 src/components/component/component-bread-crumb.js
create mode 100644 src/components/component/component-ports.js
create mode 100644 src/components/component/component-secrets.js
create mode 100644 src/components/component/env-variables.js
create mode 100644 src/components/component/job-scheduler-details.js
create mode 100644 src/components/job-overview/component-list.js
create mode 100644 src/components/page-active-component/default-alias.js
create mode 100644 src/components/page-active-component/horizontal-scaling-summary.js
create mode 100644 src/components/page-active-component/overview.js
create mode 100644 src/components/page-active-component/replica-list.js
create mode 100644 src/components/page-active-job-component/active-job-component-overview.js
create mode 100644 src/components/page-active-job-component/index.js
create mode 100644 src/components/page-active-job-component/overview.js
create mode 100644 src/components/page-active-job-component/scheduled-job-list.js
delete mode 100644 src/components/page-component/component-overview.js
create mode 100644 src/components/page-deployment-component/deployment-component-overview.js
rename src/components/{page-component => page-deployment-component}/index.js (66%)
create mode 100644 src/components/page-deployment-job-component/deployment-job-component-overview.js
create mode 100644 src/components/page-deployment-job-component/index.js
create mode 100644 src/components/page-deployment/deployment-bread-crumb.js
create mode 100644 src/components/page-deployment/deployment-component-bread-crumb.js
create mode 100644 src/components/page-deployment/deployment-component-list.js
create mode 100644 src/components/page-deployment/deployment-job-component-list.js
create mode 100644 src/components/page-deployment/deployment-summary.js
create mode 100644 src/components/page-deployment/promote-deployment-action.js
create mode 100644 src/components/page-environment/component-list-item.js
create mode 100644 src/components/page-environment/component-list.js
rename src/components/{page-job-new => page-pipeline-job-new}/index.js (77%)
rename src/components/{page-job => page-pipeline-job}/index.js (73%)
rename src/components/{page-jobs => page-pipeline-jobs}/index.js (89%)
rename src/components/{page-jobs => page-pipeline-jobs}/style.css (100%)
create mode 100644 src/components/page-scheduled-job/index.js
create mode 100644 src/components/page-scheduled-job/use-poll-logs.js
create mode 100644 src/components/page-scheduled-job/use-select-scheduled-job.js
create mode 100644 src/components/scheduled-job-status/index.js
create mode 100644 src/models/component-type/index.js
create mode 100644 src/models/component-type/normaliser.js
create mode 100644 src/models/scheduled-job-summary/index.js
create mode 100644 src/models/scheduled-job-summary/normaliser.js
create mode 100644 src/models/scheduled-job-summary/test-data.js
diff --git a/proxy/server.dev.conf b/proxy/server.dev.conf
index 1660572ef..2384336dc 100644
--- a/proxy/server.dev.conf
+++ b/proxy/server.dev.conf
@@ -6,7 +6,7 @@ server {
error_page 500 502 503 504 /50x.html;
location /api/ {
- proxy_pass https://server-radix-api-qa.dev.radix.equinor.com;
+ proxy_pass https://server-radix-api-dev.dev.radix.equinor.com;
proxy_set_header Authorization "Bearer $http_x_forwarded_access_token";
proxy_set_header x-forwarded-access-token "";
}
diff --git a/src/components/app-navbar/index.js b/src/components/app-navbar/index.js
index 58b3fad39..a9053965a 100644
--- a/src/components/app-navbar/index.js
+++ b/src/components/app-navbar/index.js
@@ -98,7 +98,7 @@ export class AppNavbar extends React.Component {
/>
0 && (
- Latest jobs
+ Latest pipeline jobs
- View all jobs
+ View all pipelene jobs
diff --git a/src/components/component/active-component-secrets.js b/src/components/component/active-component-secrets.js
new file mode 100644
index 000000000..c85bdc9a3
--- /dev/null
+++ b/src/components/component/active-component-secrets.js
@@ -0,0 +1,63 @@
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+import * as routing from '../../utils/routing';
+import SecretStatus from '../secret-status';
+import React from 'react';
+import { getEnvironment, getComponentSecret } from '../../state/environment';
+import { connect } from 'react-redux';
+import environmentModel from '../../models/environment';
+
+const ActiveComponentSecrets = ({
+ appName,
+ envName,
+ componentName,
+ secrets,
+ environment,
+}) => {
+ return (
+
+ Secrets
+ {secrets.length === 0 && This component uses no secrets
}
+ {secrets.length > 0 && (
+
+ {secrets.map((secretName) => {
+ let envSecret = getComponentSecret(
+ environment,
+ secretName,
+ componentName
+ );
+ return (
+
+
+ {secretName}
+ {' '}
+
+
+ );
+ })}
+
+ )}
+
+ );
+};
+
+ActiveComponentSecrets.propTypes = {
+ appName: PropTypes.string.isRequired,
+ envName: PropTypes.string.isRequired,
+ componentName: PropTypes.string.isRequired,
+ secrets: PropTypes.arrayOf(PropTypes.string),
+ environment: PropTypes.shape(environmentModel),
+};
+
+const mapStateToProps = (state) => ({
+ environment: getEnvironment(state),
+});
+
+export default connect(mapStateToProps)(ActiveComponentSecrets);
diff --git a/src/components/component/component-bread-crumb.js b/src/components/component/component-bread-crumb.js
new file mode 100644
index 000000000..38ad47301
--- /dev/null
+++ b/src/components/component/component-bread-crumb.js
@@ -0,0 +1,34 @@
+import Breadcrumb from '../breadcrumb';
+import { routeWithParams } from '../../utils/string';
+import routes from '../../routes';
+import * as routing from '../../utils/routing';
+import EnvironmentBadge from '../environment-badge';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const ComponentBreadCrumb = ({ appName, envName, componentName }) => {
+ return (
+ ,
+ to: routeWithParams(routes.appEnvironment, {
+ appName,
+ envName,
+ }),
+ },
+ { label: componentName },
+ ]}
+ />
+ );
+};
+
+ComponentBreadCrumb.propTypes = {
+ appName: PropTypes.string.isRequired,
+ envName: PropTypes.string.isRequired,
+ componentName: PropTypes.string.isRequired,
+};
+
+export default ComponentBreadCrumb;
diff --git a/src/components/component/component-ports.js b/src/components/component/component-ports.js
new file mode 100644
index 000000000..b2592206e
--- /dev/null
+++ b/src/components/component/component-ports.js
@@ -0,0 +1,29 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import PortModel from '../../models/port';
+
+const ComponentPorts = ({ ports }) => {
+ return (
+
+ {ports.length > 0 && (
+
+ Open ports:
+
+ {ports.map((port) => (
+
+ {port.port} ({port.name})
+
+ ))}
+
+
+ )}
+ {ports.length === 0 && No open ports
}
+
+ );
+};
+
+ComponentPorts.propTypes = {
+ ports: PropTypes.arrayOf(PropTypes.exact(PortModel)),
+};
+
+export default ComponentPorts;
diff --git a/src/components/component/component-secrets.js b/src/components/component/component-secrets.js
new file mode 100644
index 000000000..431c0fec0
--- /dev/null
+++ b/src/components/component/component-secrets.js
@@ -0,0 +1,39 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { getEnvironment } from '../../state/environment';
+import { connect } from 'react-redux';
+import { buildComponentTypeLabelMap } from '../../models/component-type';
+import Component from '../../models/component';
+
+const ComponentSecrets = ({ component }) => {
+ let componentTypeTitle = component
+ ? buildComponentTypeLabelMap(component.type)
+ : '';
+ return (
+
+
+
Secrets
+ {component && component.secrets.length === 0 && (
+
This {componentTypeTitle.toLowerCase()} uses no secrets
+ )}
+ {component && component.secrets.length > 0 && (
+
+ {component.secrets.map((secret) => (
+ {secret}
+ ))}
+
+ )}
+
+
+ );
+};
+
+ComponentSecrets.propTypes = {
+ component: PropTypes.arrayOf(PropTypes.shape(Component)),
+};
+
+const mapStateToProps = (state) => ({
+ environment: getEnvironment(state),
+});
+
+export default connect(mapStateToProps)(ComponentSecrets);
diff --git a/src/components/component/env-variables.js b/src/components/component/env-variables.js
new file mode 100644
index 000000000..4f48f55d3
--- /dev/null
+++ b/src/components/component/env-variables.js
@@ -0,0 +1,56 @@
+import React from 'react';
+
+const EnvVariables = ({ component, includeRadixVars }) => {
+ let hasRadixVars = false;
+ const envVarNames = component && Object.keys(component.variables);
+
+ const varList = envVarNames.map((varName) => {
+ const isRadixVar = varName.slice(0, 6) === 'RADIX_';
+ hasRadixVars = includeRadixVars && (hasRadixVars || isRadixVar);
+
+ if (!isRadixVar) {
+ return (
+
+ {varName}
+ {(component && component.variables)[varName]}
+
+ );
+ }
+
+ if (includeRadixVars !== true) {
+ return '';
+ }
+
+ return (
+
+
+ * {varName}
+
+
+ {(component && component.variables)[varName]}
+
+
+ );
+ });
+
+ return (
+
+ Environment variables
+ {envVarNames.length === 0 && (
+ This component uses no environment variables
+ )}
+ {envVarNames.length > 0 && (
+
+
{varList}
+ {hasRadixVars && (
+
+ * automatically added by Radix
+
+ )}
+
+ )}
+
+ );
+};
+
+export default EnvVariables;
diff --git a/src/components/component/job-scheduler-details.js b/src/components/component/job-scheduler-details.js
new file mode 100644
index 000000000..b8b0ceacf
--- /dev/null
+++ b/src/components/component/job-scheduler-details.js
@@ -0,0 +1,50 @@
+import componentModel from '../../models/component';
+import EnvVariables from '../component/env-variables';
+import PropTypes from 'prop-types';
+import React from 'react';
+import Alert from '../alert';
+
+const JobSchedulerDetails = ({ component }) => {
+ return (
+
+ Job Scheduler:
+
+
+ status {component.status}
+
+
+ port {component.schedulerPort}
+
+
+ URL{' '}
+
+ http://{component.name}:{component.schedulerPort}/api/v1
+
+
+
+ payload{' '}
+ {component.scheduledJobPayloadPath &&
+ component.scheduledJobPayloadPath.length > 0 && (
+ {component.scheduledJobPayloadPath}
+ )}
+ {!component.scheduledJobPayloadPath ||
+ (component.scheduledJobPayloadPath.length <= 0 && (
+ is empty
+ ))}
+
+
+ {component.status !== 'Consistent' && (
+
+ Job-scheduler has been manually stopped; please note that new
+ deployment will cause it to be restarted
+
+ )}
+
+ );
+};
+
+EnvVariables.propTypes = {
+ component: PropTypes.shape(componentModel),
+};
+
+export default JobSchedulerDetails;
diff --git a/src/components/configure-application-github/index.js b/src/components/configure-application-github/index.js
index 2bbd0f644..74918d2e3 100644
--- a/src/components/configure-application-github/index.js
+++ b/src/components/configure-application-github/index.js
@@ -76,7 +76,7 @@ export const ConfigureApplicationGithub = (props) => {
setSavedSharedSecret(saveState.data.sharedSecret);
resetSaveState();
onDeployKeyChange(app.name);
- }, [saveState, resetSaveState, onDeployKeyChange]);
+ }, [saveState, resetSaveState, onDeployKeyChange, app.name]);
const saveDeployKeySetting = () => {
saveFunc();
diff --git a/src/components/events-list/index.test.js b/src/components/events-list/index.test.js
index 1ba45c02f..a3a143740 100644
--- a/src/components/events-list/index.test.js
+++ b/src/components/events-list/index.test.js
@@ -184,7 +184,7 @@ const unhealthyObsolete = {
const failedNonPodEvent = {
lastTimestamp: new Date('2019-12-22T14:38:36Z'),
- involvedObjectKind: 'Job',
+ involvedObjectKind: 'Pipeline Job',
involvedObjectNamespace: 'myapp-production',
involvedObjectName: 'auth-74cb7c986',
type: 'Warning',
diff --git a/src/components/job-overview/component-list.js b/src/components/job-overview/component-list.js
new file mode 100644
index 000000000..52d1670b9
--- /dev/null
+++ b/src/components/job-overview/component-list.js
@@ -0,0 +1,26 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ComponentItem from '../../models/component-summary';
+import {
+ buildComponentMap,
+ buildComponentTypeLabelMap,
+} from '../../models/component-type';
+
+export const ComponentList = ({ components }) => {
+ let compMap = buildComponentMap(components);
+ return Object.keys(compMap).map((compType) =>
+ compMap[compType].map((component) => (
+
+ {buildComponentTypeLabelMap(compType)} {component.name}
+
+ ))
+ );
+};
+
+ComponentList.propTypes = {
+ appName: PropTypes.string.isRequired,
+ jobName: PropTypes.string.isRequired,
+ components: PropTypes.arrayOf(PropTypes.shape(ComponentItem)).isRequired,
+};
+
+export default ComponentList;
diff --git a/src/components/job-overview/index.js b/src/components/job-overview/index.js
index ff1410780..ebb6a265c 100644
--- a/src/components/job-overview/index.js
+++ b/src/components/job-overview/index.js
@@ -27,6 +27,7 @@ import {
import routes from '../../routes';
import './style.css';
+import { ComponentList } from './component-list';
const getExecutionState = (status) => {
if (status === jobStatuses.PENDING) {
@@ -77,7 +78,10 @@ const JobOverview = (props) => {
@@ -103,7 +107,7 @@ const JobOverview = (props) => {
Summary
- Job {job.status.toLowerCase()};{' '}
+ Pipeline Job {job.status.toLowerCase()};{' '}
{getExecutionState(job.status)} pipeline{' '}
{job.pipeline}
@@ -162,12 +166,9 @@ const JobOverview = (props) => {
))}
- {job.components &&
- job.components.map((component) => (
-
- Component {component.name}
-
- ))}
+ {job.components && (
+
+ )}
{job.steps && (
diff --git a/src/components/jobs-list/index.js b/src/components/jobs-list/index.js
index 40515a393..819b92f2e 100644
--- a/src/components/jobs-list/index.js
+++ b/src/components/jobs-list/index.js
@@ -25,7 +25,7 @@ const noJobsIcon = (
export const JobsList = ({ appName, jobs, limit }) => (
{jobs.length === 0 && (
-
+
Push to GitHub to trigger a job
)}
diff --git a/src/components/page-active-component/active-component-overview.js b/src/components/page-active-component/active-component-overview.js
index 46c672fd6..709441f83 100644
--- a/src/components/page-active-component/active-component-overview.js
+++ b/src/components/page-active-component/active-component-overview.js
@@ -1,74 +1,25 @@
import { connect } from 'react-redux';
-import { faLink } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import React from 'react';
-import Alert from '../alert';
-import Breadcrumb from '../breadcrumb';
-import DockerImage from '../docker-image';
-import EnvironmentBadge from '../environment-badge';
-import ReplicaStatus from '../replica-status';
-import SecretStatus from '../secret-status';
import AsyncResource from '../async-resource';
import Toolbar from './toolbar';
-
import { getAppAlias } from '../../state/application';
-import { getComponent, getSecret } from '../../state/environment';
-import { routeWithParams, smallReplicaName } from '../../utils/string';
-import * as routing from '../../utils/routing';
+import { getComponent } from '../../state/environment';
import * as subscriptionActions from '../../state/subscriptions/action-creators';
import componentModel from '../../models/component';
-import routes from '../../routes';
-
-const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME';
-
-const Vars = ({ envVarNames, component }) => {
- let hasRadixVars = false;
-
- const varList = envVarNames.map((varName) => {
- const isRadixVar = varName.slice(0, 6) === 'RADIX_';
- hasRadixVars = hasRadixVars || isRadixVar;
-
- if (isRadixVar) {
- return (
-
-
- * {varName}
-
-
- {component.variables[varName]}
-
-
- );
- }
-
- return (
-
- {varName}
- {component.variables[varName]}
-
- );
- });
-
- return (
-
-
{varList}
- {hasRadixVars && (
-
- * automatically added by Radix
-
- )}
-
- );
-};
+import EnvVariables from '../component/env-variables';
+import HorizontalScalingSummary from './horizontal-scaling-summary';
+import ComponentPorts from '../component/component-ports';
+import ReplicaList from './replica-list';
+import ComponentBreadCrumb from '../component/component-bread-crumb';
+import Overview from './overview';
+import ActiveComponentSecrets from '../component/active-component-secrets';
export class ActiveComponentOverview extends React.Component {
componentDidMount() {
this.props.subscribe(this.props.appName, this.props.envName);
}
-
componentDidUpdate(prevProps) {
const { appName, envName } = this.props;
@@ -83,37 +34,13 @@ export class ActiveComponentOverview extends React.Component {
}
render() {
- const {
- appAlias,
- appName,
- envName,
- componentName,
- component,
- getEnvSecret,
- } = this.props;
-
- const envVarNames = component && Object.keys(component.variables);
-
- const isDefaultAlias =
- appAlias &&
- appAlias.componentName === componentName &&
- appAlias.environmentName === envName;
-
+ const { appAlias, appName, envName, componentName, component } = this.props;
return (
- ,
- to: routeWithParams(routes.appEnvironment, {
- appName,
- envName,
- }),
- },
- { label: componentName },
- ]}
+
- Overview
-
- Component {component.name}
-
- {component.status === 'Stopped' && (
-
- Component has been manually stopped; please note that a
- new deployment will cause it to be restarted unless you
- set replicas
of the component to{' '}
- 0
in{' '}
-
- radixconfig.yaml
-
-
- )}
-
- Status {component.status}
-
- {component.variables[URL_VAR_NAME] && (
-
- Publically available{' '}
-
- link
-
-
- )}
- {isDefaultAlias && (
-
- This component is the application{' '}
-
- default alias{' '}
-
-
-
- )}
-
- Image
-
- {component.ports.length > 0 && (
-
- Open ports:
-
- {component.ports.map((port) => (
-
- {port.port} ({port.name})
-
- ))}
-
-
- )}
- {component.ports.length === 0 && No open ports
}
- Environment variables
- {envVarNames.length === 0 && (
- This component uses no environment variables
- )}
- {envVarNames.length > 0 && (
-
- )}
+
+
+
- {component.horizontalScalingSummary && (
-
-
- Horizontal scaling
-
-
- Min replicas:
-
- {component.horizontalScalingSummary.minReplicas}
-
- Max replicas:
-
- {component.horizontalScalingSummary.maxReplicas}
-
- Current average CPU utilization:
-
- {
- component.horizontalScalingSummary
- .currentCPUUtilizationPercentage
- }
- %
-
- Target average CPU utilization:
-
- {
- component.horizontalScalingSummary
- .targetCPUUtilizationPercentage
- }
- %
-
-
-
- )}
- Replicas
- {component.replicaList.map((replica) => (
-
-
- {smallReplicaName(replica.name)}{' '}
-
-
-
- ))}
- Secrets
- {component.secrets.length === 0 && (
- This component uses no secrets
- )}
- {component.secrets.length > 0 && (
-
- {component.secrets.map((secretName) => (
-
-
- {secretName}
- {' '}
-
-
- ))}
-
- )}
+
+
+
@@ -284,7 +103,6 @@ ActiveComponentOverview.propTypes = {
envName: PropTypes.string.isRequired,
componentName: PropTypes.string.isRequired,
component: PropTypes.shape(componentModel),
- getEnvSecret: PropTypes.func.isRequired,
subscribe: PropTypes.func.isRequired,
unsubscribe: PropTypes.func.isRequired,
};
@@ -292,7 +110,6 @@ ActiveComponentOverview.propTypes = {
const mapStateToProps = (state, { componentName }) => ({
appAlias: getAppAlias(state),
component: getComponent(state, componentName),
- getEnvSecret: (secretName) => getSecret(state, componentName, secretName),
});
const mapDispatchToProps = (dispatch) => ({
diff --git a/src/components/page-active-component/default-alias.js b/src/components/page-active-component/default-alias.js
new file mode 100644
index 000000000..779ac2735
--- /dev/null
+++ b/src/components/page-active-component/default-alias.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faLink } from '@fortawesome/free-solid-svg-icons';
+import PropTypes from 'prop-types';
+
+const DefaultAlias = (props) => {
+ const { appAlias, envName, componentName } = props;
+ const isDefaultAlias =
+ appAlias &&
+ appAlias.componentName === componentName &&
+ appAlias.environmentName === envName;
+ return (
+
+ {isDefaultAlias && (
+
+ This component is the application{' '}
+
+ default alias
+
+
+ )}
+
+ );
+};
+
+DefaultAlias.propTypes = {
+ appAlias: PropTypes.exact({
+ componentName: PropTypes.string.isRequired,
+ environmentName: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+ }),
+ envName: PropTypes.string.isRequired,
+ componentName: PropTypes.string.isRequired,
+};
+
+export default DefaultAlias;
diff --git a/src/components/page-active-component/horizontal-scaling-summary.js b/src/components/page-active-component/horizontal-scaling-summary.js
new file mode 100644
index 000000000..3c37fed0e
--- /dev/null
+++ b/src/components/page-active-component/horizontal-scaling-summary.js
@@ -0,0 +1,44 @@
+import componentModel from '../../models/component';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const HorizontalScalingSummary = (props) => {
+ const { component } = props;
+ return (
+
+ {component.horizontalScalingSummary && (
+
+ Horizontal scaling
+
+ Min replicas:
+ {component.horizontalScalingSummary.minReplicas}
+ Max replicas:
+ {component.horizontalScalingSummary.maxReplicas}
+ Current average CPU utilization:
+
+ {
+ component.horizontalScalingSummary
+ .currentCPUUtilizationPercentage
+ }
+ %
+
+ Target average CPU utilization:
+
+ {
+ component.horizontalScalingSummary
+ .targetCPUUtilizationPercentage
+ }
+ %
+
+
+
+ )}
+
+ );
+};
+
+HorizontalScalingSummary.propTypes = {
+ component: PropTypes.shape(componentModel),
+};
+
+export default HorizontalScalingSummary;
diff --git a/src/components/page-active-component/overview.js b/src/components/page-active-component/overview.js
new file mode 100644
index 000000000..8ab1e6b0c
--- /dev/null
+++ b/src/components/page-active-component/overview.js
@@ -0,0 +1,63 @@
+import Alert from '../alert';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faLink } from '@fortawesome/free-solid-svg-icons';
+import DefaultAlias from './default-alias';
+import DockerImage from '../docker-image';
+import React from 'react';
+import componentModel from '../../models/component';
+import PropTypes from 'prop-types';
+
+const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME';
+
+const Overview = ({ appAlias, envName, component }) => {
+ return (
+
+ Overview
+
+ Component {component.name}
+
+ {component.status === 'Stopped' && (
+
+ Component has been manually stopped; please note that a new deployment
+ will cause it to be restarted unless you set replicas
of
+ the component to 0
in{' '}
+
+ radixconfig.yaml
+
+
+ )}
+
+ Status {component.status}
+
+ {component.variables[URL_VAR_NAME] && (
+
+ Publicly available{' '}
+
+ link
+
+
+ )}
+ {appAlias && (
+
+ )}
+
+ Image
+
+
+ );
+};
+
+Overview.propTypes = {
+ appAlias: PropTypes.exact({
+ environmentName: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+ }),
+ envName: PropTypes.string.isRequired,
+ component: PropTypes.shape(componentModel),
+};
+
+export default Overview;
diff --git a/src/components/page-active-component/replica-list.js b/src/components/page-active-component/replica-list.js
new file mode 100644
index 000000000..ef121b8ba
--- /dev/null
+++ b/src/components/page-active-component/replica-list.js
@@ -0,0 +1,54 @@
+import PropTypes from 'prop-types';
+import ReplicaSummaryModel from '../../models/replica-summary';
+import { Link } from 'react-router-dom';
+import * as routing from '../../utils/routing';
+import { smallReplicaName } from '../../utils/string';
+import ReplicaStatus from '../replica-status';
+import RelativeToNow from '../time/relative-to-now';
+import Duration from '../time/duration';
+import React, { useEffect, useState } from 'react';
+
+const ReplicaList = ({ appName, envName, componentName, replicaList }) => {
+ const [now, setNow] = useState(new Date());
+ useEffect(() => {
+ setNow(new Date());
+ }, [replicaList]);
+ return (
+
+ Replicas
+ {replicaList &&
+ replicaList.map((replica) => (
+
+
+ {smallReplicaName(replica.name)}{' '}
+
+
+ Created{' '}
+
+
+
+ Duration{' '}
+
+
+
+
+ ))}
+
+ );
+};
+
+ReplicaList.propTypes = {
+ appName: PropTypes.string.isRequired,
+ envName: PropTypes.string.isRequired,
+ componentName: PropTypes.string.isRequired,
+ replicaList: PropTypes.arrayOf(PropTypes.exact(ReplicaSummaryModel)),
+};
+
+export default ReplicaList;
diff --git a/src/components/page-active-job-component/active-job-component-overview.js b/src/components/page-active-job-component/active-job-component-overview.js
new file mode 100644
index 000000000..718768fb8
--- /dev/null
+++ b/src/components/page-active-job-component/active-job-component-overview.js
@@ -0,0 +1,114 @@
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import AsyncResource from '../async-resource';
+import { getComponent } from '../../state/environment';
+import * as subscriptionActions from '../../state/subscriptions/action-creators';
+import componentModel from '../../models/component';
+import EnvVariables from '../component/env-variables';
+import ComponentPorts from '../component/component-ports';
+import ComponentBreadCrumb from '../component/component-bread-crumb';
+import ScheduledJobList from './scheduled-job-list';
+import JobSchedulerDetails from '../component/job-scheduler-details';
+import Overview from './overview';
+import ActiveComponentSecrets from '../component/active-component-secrets';
+
+export class ActiveScheduledJobOverview extends React.Component {
+ componentDidMount() {
+ this.props.subscribe(this.props.appName, this.props.envName);
+ }
+ componentDidUpdate(prevProps) {
+ const { appName, envName } = this.props;
+
+ if (appName !== prevProps.appName || envName !== prevProps.envName) {
+ this.props.unsubscribe(prevProps.appName, prevProps.envName);
+ this.props.subscribe(appName, envName);
+ }
+ }
+
+ componentWillUnmount() {
+ this.props.unsubscribe(this.props.appName, this.props.envName);
+ }
+
+ render() {
+ const { appName, envName, jobComponentName, component } = this.props;
+
+ return (
+
+
+
+
+ {component && (
+
+
+
+ )}
+
+
+
+ );
+ }
+}
+
+ActiveScheduledJobOverview.propTypes = {
+ appName: PropTypes.string.isRequired,
+ envName: PropTypes.string.isRequired,
+ jobComponentName: PropTypes.string.isRequired,
+ component: PropTypes.shape(componentModel),
+ subscribe: PropTypes.func.isRequired,
+ unsubscribe: PropTypes.func.isRequired,
+};
+
+const mapStateToProps = (state, { jobComponentName }) => ({
+ component: getComponent(state, jobComponentName),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ subscribe: (appName, envName) => {
+ dispatch(subscriptionActions.subscribeEnvironment(appName, envName));
+ dispatch(subscriptionActions.subscribeApplication(appName));
+ },
+ unsubscribe: (appName, envName) => {
+ dispatch(subscriptionActions.unsubscribeEnvironment(appName, envName));
+ dispatch(subscriptionActions.unsubscribeApplication(appName));
+ },
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ActiveScheduledJobOverview);
diff --git a/src/components/page-active-job-component/index.js b/src/components/page-active-job-component/index.js
new file mode 100644
index 000000000..c4df5bdff
--- /dev/null
+++ b/src/components/page-active-job-component/index.js
@@ -0,0 +1,39 @@
+import { Route } from 'react-router';
+import React from 'react';
+
+import ActiveJobComponentOverview from './active-job-component-overview';
+
+import DocumentTitle from '../document-title';
+import PageSecret from '../page-secret';
+
+import { mapRouteParamsToProps } from '../../utils/routing';
+import routes from '../../routes';
+import PageScheduledJob from '../page-scheduled-job';
+
+export const PageActiveJobComponent = ({
+ appName,
+ envName,
+ jobComponentName,
+}) => (
+
+
+ (
+
+ )}
+ />
+
+
+
+);
+
+export default mapRouteParamsToProps(
+ ['appName', 'envName', 'jobComponentName'],
+ PageActiveJobComponent
+);
diff --git a/src/components/page-active-job-component/overview.js b/src/components/page-active-job-component/overview.js
new file mode 100644
index 000000000..8d5f10363
--- /dev/null
+++ b/src/components/page-active-job-component/overview.js
@@ -0,0 +1,24 @@
+import DockerImage from '../docker-image';
+import React from 'react';
+import componentModel from '../../models/component';
+import PropTypes from 'prop-types';
+
+const Overview = ({ component }) => {
+ return (
+
+ Overview
+
+ Job {component.name}
+
+
+ Image
+
+
+ );
+};
+
+Overview.propTypes = {
+ component: PropTypes.shape(componentModel),
+};
+
+export default Overview;
diff --git a/src/components/page-active-job-component/scheduled-job-list.js b/src/components/page-active-job-component/scheduled-job-list.js
new file mode 100644
index 000000000..9c9a118be
--- /dev/null
+++ b/src/components/page-active-job-component/scheduled-job-list.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+import * as routing from '../../utils/routing';
+import { smallScheduledJobName } from '../../utils/string';
+import ScheduledJobStatus from '../scheduled-job-status';
+import RelativeToNow from '../time/relative-to-now';
+import ScheduledJobSummaryModel from '../../models/scheduled-job-summary';
+
+const ScheduledJobList = ({
+ appName,
+ envName,
+ jobComponentName,
+ scheduledJobList,
+}) => {
+ return (
+
+ Scheduled Job
+ {scheduledJobList.map((scheduledJob) => (
+
+
+ {smallScheduledJobName(scheduledJob.name)}{' '}
+
+
+ Created{' '}
+
+
+
+
+ ))}
+
+ );
+};
+
+ScheduledJobList.propTypes = {
+ appName: PropTypes.string.isRequired,
+ envName: PropTypes.string.isRequired,
+ jobComponentName: PropTypes.string.isRequired,
+ scheduledJobList: PropTypes.arrayOf(
+ PropTypes.exact(ScheduledJobSummaryModel)
+ ),
+};
+
+export default ScheduledJobList;
diff --git a/src/components/page-application/index.js b/src/components/page-application/index.js
index 70ca178d2..613eeb61a 100644
--- a/src/components/page-application/index.js
+++ b/src/components/page-application/index.js
@@ -16,9 +16,9 @@ import PageEnvironment from '../page-environment';
import PageEnvironments from '../page-environments';
import PageBuildSecret from '../page-build-secret';
import PagePrivateImageHub from '../page-private-image-hub';
-import PageJob from '../page-job';
-import PageJobNew from '../page-job-new';
-import PageJobs from '../page-jobs';
+import PageJob from '../page-pipeline-job';
+import PageJobNew from '../page-pipeline-job-new';
+import PageJobs from '../page-pipeline-jobs';
import { mapRouteParamsToProps } from '../../utils/routing';
import routes from '../../routes';
diff --git a/src/components/page-component/component-overview.js b/src/components/page-component/component-overview.js
deleted file mode 100644
index b940ef995..000000000
--- a/src/components/page-component/component-overview.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Breadcrumb from '../breadcrumb';
-import DockerImage from '../docker-image';
-import AsyncResource from '../async-resource';
-
-import { getDeployment } from '../../state/deployment';
-import { routeWithParams, smallDeploymentName } from '../../utils/string';
-import * as actionCreators from '../../state/subscriptions/action-creators';
-import routes from '../../routes';
-
-/**
- * Filter for removing Radix-injected variables; those should not be displayed
- * in a deployment coponent (only in a running component under an environment)
- */
-const filterRadixVariables = (() => {
- const radixVarRegEx = /^RADIX_/;
- return (varName) => !varName.match(radixVarRegEx);
-})();
-
-export class DeploymentOverview extends React.Component {
- componentDidMount() {
- this.props.subscribe(this.props.appName, this.props.deploymentName);
- }
-
- componentDidUpdate(prevProps) {
- const { appName, deploymentName } = this.props;
-
- if (
- appName !== prevProps.appName ||
- deploymentName !== prevProps.deploymentName
- ) {
- this.props.unsubscribe(prevProps.appName, prevProps.deploymentName);
- this.props.subscribe(appName, deploymentName);
- }
- }
-
- componentWillUnmount() {
- this.props.unsubscribe(this.props.appName, this.props.deploymentName);
- }
-
- render() {
- const { appName, componentName, deploymentName, deployment } = this.props;
- const component =
- deployment &&
- deployment.components &&
- deployment.components.find((comp) => comp.name === componentName);
- const envVarNames =
- component &&
- Object.keys(component.variables).filter(filterRadixVariables);
-
- return (
-
-
-
-
- {deployment && (
-
-
- Overview
-
- Component {component.name}
-
-
- Image
-
- {component.ports.length > 0 && (
-
- Open ports:
-
- {component.ports.map((port) => (
-
- {port.port} ({port.name})
-
- ))}
-
-
- )}
- {component.ports.length === 0 && No open ports
}
-
-
-
- Environment variables
- {envVarNames.length === 0 && (
- This component uses no environment variables
- )}
- {envVarNames.length > 0 && (
-
- {envVarNames.map((varName) => (
-
- {varName}
- {component.variables[varName]}
-
- ))}
-
- )}
-
-
- Secrets
- {component.secrets.length === 0 && (
- This component uses no secrets
- )}
- {component.secrets.length > 0 && (
-
- {component.secrets.map((secret) => (
- {secret}
- ))}
-
- )}
-
-
-
- )}
-
-
-
- );
- }
-}
-
-DeploymentOverview.propTypes = {
- appName: PropTypes.string.isRequired,
- componentName: PropTypes.string.isRequired,
- deployment: PropTypes.object,
- deploymentName: PropTypes.string.isRequired,
- subscribe: PropTypes.func.isRequired,
- unsubscribe: PropTypes.func.isRequired,
-};
-
-const mapStateToProps = (state) => ({
- deployment: getDeployment(state),
-});
-
-const mapDispatchToProps = (dispatch) => ({
- subscribe: (appName, deploymentName) => {
- dispatch(actionCreators.subscribeDeployment(appName, deploymentName));
- },
- unsubscribe: (appName, deploymentName) => {
- dispatch(actionCreators.unsubscribeDeployment(appName, deploymentName));
- },
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(DeploymentOverview);
diff --git a/src/components/page-configuration/change-config-branch-form.js b/src/components/page-configuration/change-config-branch-form.js
index 41a76289d..864023d7d 100644
--- a/src/components/page-configuration/change-config-branch-form.js
+++ b/src/components/page-configuration/change-config-branch-form.js
@@ -75,7 +75,7 @@ export const ChangeConfigBranchForm = (props) => {
- Jobs
+ Pipeline Jobs
{' '}
to verify that the build-deploy job runs to completion
diff --git a/src/components/page-deployment-component/deployment-component-overview.js b/src/components/page-deployment-component/deployment-component-overview.js
new file mode 100644
index 000000000..cdffe70f9
--- /dev/null
+++ b/src/components/page-deployment-component/deployment-component-overview.js
@@ -0,0 +1,106 @@
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import React from 'react';
+import AsyncResource from '../async-resource';
+import { getDeployment } from '../../state/deployment';
+import * as actionCreators from '../../state/subscriptions/action-creators';
+import ComponentSecrets from '../component/component-secrets';
+import EnvVariables from '../component/env-variables';
+import DeploymentComponentBreadCrumb from '../page-deployment/deployment-component-bread-crumb';
+import ComponentPorts from '../component/component-ports';
+import Overview from '../page-active-component/overview';
+
+export class DeploymentComponentOverview extends React.Component {
+ componentDidMount() {
+ this.props.subscribe(this.props.appName, this.props.deploymentName);
+ }
+
+ componentDidUpdate(prevProps) {
+ const { appName, deploymentName } = this.props;
+
+ if (
+ appName !== prevProps.appName ||
+ deploymentName !== prevProps.deploymentName
+ ) {
+ this.props.unsubscribe(prevProps.appName, prevProps.deploymentName);
+ this.props.subscribe(appName, deploymentName);
+ }
+ }
+
+ componentWillUnmount() {
+ this.props.unsubscribe(this.props.appName, this.props.deploymentName);
+ }
+
+ render() {
+ const { appName, deploymentName, componentName, deployment } = this.props;
+ const component =
+ deployment &&
+ deployment.components &&
+ deployment.components.find((comp) => comp.name === componentName);
+ return (
+
+
+
+
+ {deployment && (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ );
+ }
+}
+
+DeploymentComponentOverview.propTypes = {
+ appName: PropTypes.string.isRequired,
+ componentName: PropTypes.string.isRequired,
+ deployment: PropTypes.object,
+ deploymentName: PropTypes.string.isRequired,
+ subscribe: PropTypes.func.isRequired,
+ unsubscribe: PropTypes.func.isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ deployment: getDeployment(state),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ subscribe: (appName, deploymentName) => {
+ dispatch(actionCreators.subscribeDeployment(appName, deploymentName));
+ },
+ unsubscribe: (appName, deploymentName) => {
+ dispatch(actionCreators.unsubscribeDeployment(appName, deploymentName));
+ },
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(DeploymentComponentOverview);
diff --git a/src/components/page-component/index.js b/src/components/page-deployment-component/index.js
similarity index 66%
rename from src/components/page-component/index.js
rename to src/components/page-deployment-component/index.js
index 01dac8cf6..8f52f4e54 100644
--- a/src/components/page-component/index.js
+++ b/src/components/page-deployment-component/index.js
@@ -1,19 +1,21 @@
import React from 'react';
-import ComponentOverview from './component-overview';
-
+import DeploymentComponentOverview from './deployment-component-overview';
import DocumentTitle from '../document-title';
-
import { mapRouteParamsToProps } from '../../utils/routing';
-export const PageComponent = ({ appName, deploymentName, componentName }) => {
+export const PageDeploymentComponent = ({
+ appName,
+ deploymentName,
+ componentName,
+}) => {
return (
-
);
@@ -21,5 +23,5 @@ export const PageComponent = ({ appName, deploymentName, componentName }) => {
export default mapRouteParamsToProps(
['appName', 'deploymentName', 'componentName'],
- PageComponent
+ PageDeploymentComponent
);
diff --git a/src/components/page-deployment-job-component/deployment-job-component-overview.js b/src/components/page-deployment-job-component/deployment-job-component-overview.js
new file mode 100644
index 000000000..76961f733
--- /dev/null
+++ b/src/components/page-deployment-job-component/deployment-job-component-overview.js
@@ -0,0 +1,111 @@
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import React from 'react';
+import DockerImage from '../docker-image';
+import AsyncResource from '../async-resource';
+import { getDeployment } from '../../state/deployment';
+import * as actionCreators from '../../state/subscriptions/action-creators';
+import ComponentSecrets from '../component/component-secrets';
+import EnvVariables from '../component/env-variables';
+import DeploymentComponentBreadCrumb from '../page-deployment/deployment-component-bread-crumb';
+import ComponentPorts from '../component/component-ports';
+import JobSchedulerDetails from '../component/job-scheduler-details';
+import Overview from '../page-active-job-component/overview';
+
+export class DeploymentJobComponentOverview extends React.Component {
+ componentDidMount() {
+ this.props.subscribe(this.props.appName, this.props.deploymentName);
+ }
+
+ componentDidUpdate(prevProps) {
+ const { appName, deploymentName } = this.props;
+
+ if (
+ appName !== prevProps.appName ||
+ deploymentName !== prevProps.deploymentName
+ ) {
+ this.props.unsubscribe(prevProps.appName, prevProps.deploymentName);
+ this.props.subscribe(appName, deploymentName);
+ }
+ }
+
+ componentWillUnmount() {
+ this.props.unsubscribe(this.props.appName, this.props.deploymentName);
+ }
+
+ render() {
+ const {
+ appName,
+ jobComponentName,
+ deploymentName,
+ deployment,
+ } = this.props;
+ const component =
+ deployment &&
+ deployment.components &&
+ deployment.components.find((comp) => comp.name === jobComponentName);
+ return (
+
+
+
+
+ {deployment && component && (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ );
+ }
+}
+
+DeploymentJobComponentOverview.propTypes = {
+ appName: PropTypes.string.isRequired,
+ jobComponentName: PropTypes.string.isRequired,
+ deployment: PropTypes.object,
+ deploymentName: PropTypes.string.isRequired,
+ subscribe: PropTypes.func.isRequired,
+ unsubscribe: PropTypes.func.isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ deployment: getDeployment(state),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ subscribe: (appName, deploymentName) => {
+ dispatch(actionCreators.subscribeDeployment(appName, deploymentName));
+ },
+ unsubscribe: (appName, deploymentName) => {
+ dispatch(actionCreators.unsubscribeDeployment(appName, deploymentName));
+ },
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(DeploymentJobComponentOverview);
diff --git a/src/components/page-deployment-job-component/index.js b/src/components/page-deployment-job-component/index.js
new file mode 100644
index 000000000..e39ec7c2e
--- /dev/null
+++ b/src/components/page-deployment-job-component/index.js
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import DeploymentJobComponentOverview from './deployment-job-component-overview';
+import DocumentTitle from '../document-title';
+import { mapRouteParamsToProps } from '../../utils/routing';
+
+export const PageDeploymentJobComponent = ({
+ appName,
+ deploymentName,
+ jobComponentName,
+}) => {
+ return (
+
+
+
+
+ );
+};
+
+export default mapRouteParamsToProps(
+ ['appName', 'deploymentName', 'jobComponentName'],
+ PageDeploymentJobComponent
+);
diff --git a/src/components/page-deployment/deployment-bread-crumb.js b/src/components/page-deployment/deployment-bread-crumb.js
new file mode 100644
index 000000000..7e410a17e
--- /dev/null
+++ b/src/components/page-deployment/deployment-bread-crumb.js
@@ -0,0 +1,27 @@
+import Breadcrumb from '../breadcrumb';
+import { routeWithParams, smallDeploymentName } from '../../utils/string';
+import routes from '../../routes';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const DeploymentBreadCrumb = ({ appName, deploymentName }) => {
+ return (
+
+ );
+};
+
+DeploymentBreadCrumb.propTypes = {
+ appName: PropTypes.string.isRequired,
+ deploymentName: PropTypes.string.isRequired,
+};
+
+export default DeploymentBreadCrumb;
diff --git a/src/components/page-deployment/deployment-component-bread-crumb.js b/src/components/page-deployment/deployment-component-bread-crumb.js
new file mode 100644
index 000000000..77b35d69a
--- /dev/null
+++ b/src/components/page-deployment/deployment-component-bread-crumb.js
@@ -0,0 +1,41 @@
+import Breadcrumb from '../breadcrumb';
+import { routeWithParams, smallDeploymentName } from '../../utils/string';
+import routes from '../../routes';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const DeploymentComponentBreadCrumb = ({
+ appName,
+ deploymentName,
+ componentName,
+}) => {
+ return (
+
+ );
+};
+
+DeploymentComponentBreadCrumb.propTypes = {
+ appName: PropTypes.string.isRequired,
+ deploymentName: PropTypes.string.isRequired,
+ componentName: PropTypes.string.isRequired,
+};
+
+export default DeploymentComponentBreadCrumb;
diff --git a/src/components/page-deployment/deployment-component-list.js b/src/components/page-deployment/deployment-component-list.js
new file mode 100644
index 000000000..770772961
--- /dev/null
+++ b/src/components/page-deployment/deployment-component-list.js
@@ -0,0 +1,44 @@
+import Component from '../../models/component';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+import { routeWithParams } from '../../utils/string';
+import routes from '../../routes';
+import DockerImage from '../docker-image';
+import React from 'react';
+
+const DeploymentComponentList = ({ appName, deploymentName, components }) => {
+ return (
+
+ {components && (
+
+ Components
+ {components.map((component) => {
+ return (
+
+
+ {component.name}
+
+
+ image
+
+ );
+ })}
+
+ )}
+
+ );
+};
+
+DeploymentComponentList.propTypes = {
+ appName: PropTypes.string.isRequired,
+ deploymentName: PropTypes.string.isRequired,
+ components: PropTypes.arrayOf(PropTypes.shape(Component)),
+};
+
+export default DeploymentComponentList;
diff --git a/src/components/page-deployment/deployment-job-component-list.js b/src/components/page-deployment/deployment-job-component-list.js
new file mode 100644
index 000000000..311b656aa
--- /dev/null
+++ b/src/components/page-deployment/deployment-job-component-list.js
@@ -0,0 +1,48 @@
+import Component from '../../models/component';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+import { routeWithParams } from '../../utils/string';
+import routes from '../../routes';
+import DockerImage from '../docker-image';
+import React from 'react';
+
+const DeploymentJobComponentList = ({
+ appName,
+ deploymentName,
+ components,
+}) => {
+ return (
+
+ {components && (
+
+ Jobs
+ {components.map((component) => {
+ return (
+
+
+ {component.name}
+
+
+ image
+
+ );
+ })}
+
+ )}
+
+ );
+};
+
+DeploymentJobComponentList.propTypes = {
+ appName: PropTypes.string.isRequired,
+ deploymentName: PropTypes.string.isRequired,
+ components: PropTypes.arrayOf(PropTypes.shape(Component)),
+};
+
+export default DeploymentJobComponentList;
diff --git a/src/components/page-deployment/deployment-overview.js b/src/components/page-deployment/deployment-overview.js
index 4f4c247fa..99bf02a04 100644
--- a/src/components/page-deployment/deployment-overview.js
+++ b/src/components/page-deployment/deployment-overview.js
@@ -1,30 +1,19 @@
import { connect } from 'react-redux';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import React from 'react';
-
-import ActionsPage from '../actions-page';
import Alert from '../alert';
import AsyncResource from '../async-resource';
-import Breadcrumb from '../breadcrumb';
-import DockerImage from '../docker-image';
-import LinkButton from '../link-button';
-import RelativeToNow from '../time/relative-to-now';
-
-import {
- routeWithParams,
- smallDeploymentName,
- smallJobName,
-} from '../../utils/string';
import { getDeployment } from '../../state/deployment';
import * as actionCreators from '../../state/subscriptions/action-creators';
import deploymentModel from '../../models/deployment';
-import configHandler from '../../utils/config';
-import { keys as configKeys } from '../../utils/config/keys';
-
-import routes from '../../routes';
+import DeploymentSummary from './deployment-summary';
+import { buildComponentMap, componentType } from '../../models/component-type';
+import DeploymentComponentList from './deployment-component-list';
+import DeploymentJobComponentList from './deployment-job-component-list';
+import DeploymentBreadcrumb from '../page-deployment/deployment-bread-crumb';
+import PromoteDeploymentAction from './promote-deployment-action';
export class DeploymentOverview extends React.Component {
componentDidMount() {
@@ -49,38 +38,23 @@ export class DeploymentOverview extends React.Component {
render() {
const { appName, deploymentName, deployment } = this.props;
+ let componentMap;
+ if (deployment) {
+ componentMap = buildComponentMap(deployment.components);
+ }
return (
-
+
- {deployment &&
- configHandler.getConfig(configKeys.FLAGS).enablePromotionPipeline && (
-
-
- Promote deployment…
-
-
- )}
-
+
-
- Summary
-
- {!deployment.activeTo && (
-
- Currently deployed on environment{' '}
-
- )}
- {deployment.activeTo && (
-
- Was deployed to environment{' '}
-
- )}
-
- {deployment.environment}
-
-
- {deployment.createdByJob && (
-
- Created by job{' '}
-
- {smallJobName(deployment.createdByJob)}
-
-
- )}
-
- Active from{' '}
-
-
-
-
- {deployment.activeTo && (
-
- Active until{' '}
-
-
-
-
- )}
-
-
- Components
- {deployment.components &&
- deployment.components.map((component) => (
-
-
- {component.name}
-
-
- image
-
- ))}
-
+
+
+
)}
diff --git a/src/components/page-deployment/deployment-summary.js b/src/components/page-deployment/deployment-summary.js
new file mode 100644
index 000000000..d2f536a7c
--- /dev/null
+++ b/src/components/page-deployment/deployment-summary.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { routeWithParams, smallJobName } from '../../utils/string';
+import routes from '../../routes';
+import RelativeToNow from '../time/relative-to-now';
+import deploymentModel from '../../models/deployment';
+import PropTypes from 'prop-types';
+
+const DeploymentSummary = ({ appName, deployment }) => {
+ return (
+
+ Summary
+
+ {!deployment.activeTo && (
+
+ Currently deployed on environment{' '}
+
+ )}
+ {deployment.activeTo && (
+ Was deployed to environment
+ )}
+
+ {deployment.environment}
+
+
+ {deployment.createdByJob && (
+
+ Created by pipeline job{' '}
+
+ {smallJobName(deployment.createdByJob)}
+
+
+ )}
+
+ Active from{' '}
+
+
+
+
+ {deployment.activeTo && (
+
+ Active until{' '}
+
+
+
+
+ )}
+
+ );
+};
+
+DeploymentSummary.propTypes = {
+ appName: PropTypes.string.isRequired,
+ deployment: PropTypes.exact(deploymentModel),
+};
+
+export default DeploymentSummary;
diff --git a/src/components/page-deployment/index.js b/src/components/page-deployment/index.js
index 666bb2eb7..6184bc981 100644
--- a/src/components/page-deployment/index.js
+++ b/src/components/page-deployment/index.js
@@ -4,11 +4,11 @@ import React from 'react';
import DeploymentOverview from './deployment-overview';
import DocumentTitle from '../document-title';
-import PageComponent from '../page-component';
-
import { mapRouteParamsToProps } from '../../utils/routing';
import { smallDeploymentName } from '../../utils/string';
import routes from '../../routes';
+import PageDeploymentComponent from '../page-deployment-component';
+import PageDeploymentJobComponent from '../page-deployment-job-component';
export const PageDeployment = ({ appName, deploymentName }) => {
return (
@@ -26,7 +26,11 @@ export const PageDeployment = ({ appName, deploymentName }) => {
/>
)}
/>
-
+
+
);
};
diff --git a/src/components/page-deployment/promote-deployment-action.js b/src/components/page-deployment/promote-deployment-action.js
new file mode 100644
index 000000000..75a7ee768
--- /dev/null
+++ b/src/components/page-deployment/promote-deployment-action.js
@@ -0,0 +1,42 @@
+import deploymentModel from '../../models/deployment';
+import PropTypes from 'prop-types';
+import configHandler from '../../utils/config';
+import { keys as configKeys } from '../../utils/config/keys';
+import ActionsPage from '../actions-page';
+import LinkButton from '../link-button';
+import { routeWithParams } from '../../utils/string';
+import routes from '../../routes';
+import React from 'react';
+
+const PromoteDeploymentAction = ({ appName, deploymentName, deployment }) => {
+ return (
+
+ {deployment &&
+ configHandler.getConfig(configKeys.FLAGS).enablePromotionPipeline && (
+
+
+ Promote deployment…
+
+
+ )}
+
+ );
+};
+
+PromoteDeploymentAction.propTypes = {
+ appName: PropTypes.string.isRequired,
+ deploymentName: PropTypes.string.isRequired,
+ deployment: PropTypes.exact(deploymentModel),
+};
+
+export default PromoteDeploymentAction;
diff --git a/src/components/page-environment/component-list-item.js b/src/components/page-environment/component-list-item.js
new file mode 100644
index 000000000..5fbdef102
--- /dev/null
+++ b/src/components/page-environment/component-list-item.js
@@ -0,0 +1,51 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ComponentItem from '../../models/component-summary';
+import { Link } from 'react-router-dom';
+import * as routing from '../../utils/routing';
+import ActiveComponentStatus from './active-component-status';
+import environmentModel from '../../models/environment';
+import { componentType } from '../../models/component-type';
+
+export const ComponentListItem = ({ appName, environment, components }) => {
+ return components.map((component) => {
+ let activeComponentUrl = getActiveComponentUrl(
+ appName,
+ environment,
+ component
+ );
+ return (
+
+ {component.name}
+
+
+ );
+ });
+};
+
+function getActiveComponentUrl(appName, environment, component) {
+ if (component.type === componentType.job)
+ return routing.getActiveJobComponentUrl(
+ appName,
+ environment.name,
+ component.name
+ );
+ return routing.getActiveComponentUrl(
+ appName,
+ environment.name,
+ component.name
+ );
+}
+
+ComponentListItem.propTypes = {
+ appName: PropTypes.string.isRequired,
+ environment: PropTypes.shape(environmentModel),
+ components: PropTypes.arrayOf(PropTypes.shape(ComponentItem)).isRequired,
+};
+
+export default ComponentListItem;
diff --git a/src/components/page-environment/component-list.js b/src/components/page-environment/component-list.js
new file mode 100644
index 000000000..d4d4ebda6
--- /dev/null
+++ b/src/components/page-environment/component-list.js
@@ -0,0 +1,33 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ComponentItem from '../../models/component-summary';
+import {
+ buildComponentMap,
+ buildComponentTypeLabelPluralMap,
+} from '../../models/component-type';
+import environmentModel from '../../models/environment';
+import ComponentListItem from './component-list-item';
+
+export const ComponentList = ({ appName, environment, components }) => {
+ const compMap = buildComponentMap(components);
+ return Object.keys(compMap).map((componentType) => (
+
+
+ Active {buildComponentTypeLabelPluralMap(componentType)}
+
+
+
+ ));
+};
+
+ComponentList.propTypes = {
+ appName: PropTypes.string.isRequired,
+ environment: PropTypes.shape(environmentModel),
+ components: PropTypes.arrayOf(PropTypes.shape(ComponentItem)).isRequired,
+};
+
+export default ComponentList;
diff --git a/src/components/page-environment/environment-overview.js b/src/components/page-environment/environment-overview.js
index 04a833853..5eed74cc0 100644
--- a/src/components/page-environment/environment-overview.js
+++ b/src/components/page-environment/environment-overview.js
@@ -5,7 +5,6 @@ import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import React from 'react';
-import ActiveComponentStatus from './active-component-status';
import Alert from '../alert';
import Button from '../button';
import LinkButton from '../link-button';
@@ -36,6 +35,7 @@ import { keys as configKeys } from '../../utils/config/keys';
import routes from '../../routes';
import './style.css';
+import ComponentList from './component-list';
const eventDateSorter = (a, b) => {
if (a.lastTimestamp > b.lastTimestamp) {
@@ -196,29 +196,11 @@ export class EnvironmentOverview extends React.Component {
)}
{deployment && (
-
- Active components
- {deployment.components &&
- deployment.components.map((component) => (
-
-
- {component.name}{' '}
-
-
-
- ))}
-
+
)}
diff --git a/src/components/page-environment/index.js b/src/components/page-environment/index.js
index 1da0e7efb..a580d4506 100644
--- a/src/components/page-environment/index.js
+++ b/src/components/page-environment/index.js
@@ -5,6 +5,7 @@ import EnvironmentOverview from './environment-overview';
import DocumentTitle from '../document-title';
import PageActiveComponent from '../page-active-component';
+import PageActiveJobComponent from '../page-active-job-component';
import { mapRouteParamsToProps } from '../../utils/routing';
import routes from '../../routes';
@@ -21,6 +22,10 @@ export const PageEnvironment = ({ appName, envName }) => {
)}
/>
+
);
};
diff --git a/src/components/page-job-new/index.js b/src/components/page-pipeline-job-new/index.js
similarity index 77%
rename from src/components/page-job-new/index.js
rename to src/components/page-pipeline-job-new/index.js
index 4ee55cd36..a465ba07e 100644
--- a/src/components/page-job-new/index.js
+++ b/src/components/page-pipeline-job-new/index.js
@@ -17,7 +17,7 @@ import routes from '../../routes';
import jobActions from '../../state/job-creation/action-creators';
import { getCreationResult, getCreationState } from '../../state/job-creation';
-class PageJobNew extends React.Component {
+class PagePipelineJobNew extends React.Component {
componentWillUnmount() {
this.props.resetCreate();
}
@@ -27,20 +27,23 @@ class PageJobNew extends React.Component {
return (
-
+
-
New job
+
New pipeline job
- Jobs perform different actions in Radix. The pipeline of the job
- defines what action to take, and it may require specific
+ Pipeline jobs perform different actions in Radix. The pipeline of
+ the job defines what action to take, and it may require specific
parameters.
@@ -66,7 +69,7 @@ class PageJobNew extends React.Component {
jobName: this.props.creationResult.name,
})}
>
- Job
+ Pipeline Job
);
@@ -76,14 +79,14 @@ class PageJobNew extends React.Component {
appName: appName,
})}
>
- jobs
+ Pipeline Jobs
);
return (
- The job "{this.props.creationResult.name}" has been created
+ The pipeline job "{this.props.creationResult.name}" has been created
View {jobLink} or all {jobsLink}
@@ -93,7 +96,7 @@ class PageJobNew extends React.Component {
}
}
-PageJobNew.propTypes = {
+PagePipelineJobNew.propTypes = {
appName: PropTypes.string.isRequired,
creationState: PropTypes.oneOf(Object.values(requestStates)).isRequired,
resetCreate: PropTypes.func.isRequired,
@@ -110,5 +113,5 @@ const mapDispatchToProps = (dispatch) => ({
export default mapRouteParamsToProps(
['appName'],
- connect(mapStateToProps, mapDispatchToProps)(PageJobNew)
+ connect(mapStateToProps, mapDispatchToProps)(PagePipelineJobNew)
);
diff --git a/src/components/page-job/index.js b/src/components/page-pipeline-job/index.js
similarity index 73%
rename from src/components/page-job/index.js
rename to src/components/page-pipeline-job/index.js
index 9dced18c3..9e28de456 100644
--- a/src/components/page-job/index.js
+++ b/src/components/page-pipeline-job/index.js
@@ -8,10 +8,10 @@ import PageStep from '../page-step';
import { mapRouteParamsToProps } from '../../utils/routing';
import routes from '../../routes';
-export const PageJob = ({ appName, jobName }) => {
+export const PipelinePageJob = ({ appName, jobName }) => {
return (
-
+
{
);
};
-export default mapRouteParamsToProps(['appName', 'jobName'], PageJob);
+export default mapRouteParamsToProps(['appName', 'jobName'], PipelinePageJob);
diff --git a/src/components/page-jobs/index.js b/src/components/page-pipeline-jobs/index.js
similarity index 89%
rename from src/components/page-jobs/index.js
rename to src/components/page-pipeline-jobs/index.js
index 1113de59c..5d0999187 100644
--- a/src/components/page-jobs/index.js
+++ b/src/components/page-pipeline-jobs/index.js
@@ -18,7 +18,7 @@ import routes from '../../routes';
import './style.css';
-class PageJobs extends React.Component {
+class PipelinePageJobs extends React.Component {
componentDidMount() {
const { subscribeJobs, appName, envName } = this.props;
subscribeJobs(appName, envName);
@@ -42,17 +42,17 @@ class PageJobs extends React.Component {
const { appName, jobs } = this.props;
return (
-
+
- New Job…
+ New Pipeline Job…
@@ -64,7 +64,7 @@ class PageJobs extends React.Component {
}
}
-PageJobs.propTypes = {
+PipelinePageJobs.propTypes = {
appName: PropTypes.string.isRequired,
jobs: PropTypes.arrayOf(PropTypes.shape(jobSummaryModel)).isRequired,
};
@@ -82,5 +82,5 @@ const mapDispatchToProps = (dispatch) => ({
export default mapRouteParamsToProps(
['appName'],
- connect(mapStateToProps, mapDispatchToProps)(PageJobs)
+ connect(mapStateToProps, mapDispatchToProps)(PipelinePageJobs)
);
diff --git a/src/components/page-jobs/style.css b/src/components/page-pipeline-jobs/style.css
similarity index 100%
rename from src/components/page-jobs/style.css
rename to src/components/page-pipeline-jobs/style.css
diff --git a/src/components/page-replica/index.js b/src/components/page-replica/index.js
index 1ee7037ff..df9340c78 100644
--- a/src/components/page-replica/index.js
+++ b/src/components/page-replica/index.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import useGetEnvironment from '../page-environment/use-get-environment';
@@ -15,22 +15,18 @@ import routes from '../../routes';
import { mapRouteParamsToProps } from '../../utils/routing';
import { routeWithParams, smallReplicaName } from '../../utils/string';
import * as routing from '../../utils/routing';
+import RelativeToNow from '../time/relative-to-now';
+import Duration from '../time/duration';
const STATUS_OK = 'Running';
const PageReplica = (props) => {
- const {
- appName,
- envName,
- deploymentName,
- componentName,
- replicaName,
- } = props;
+ const { appName, envName, componentName, replicaName } = props;
const [getEnvironmentState] = useGetEnvironment(appName, envName);
const [pollLogsState] = usePollLogs(
appName,
- deploymentName,
+ envName,
componentName,
replicaName
);
@@ -39,8 +35,12 @@ const PageReplica = (props) => {
componentName,
replicaName
);
- const replicaStatus = replica ? replica.replicaStatus : null;
+ const [now, setNow] = useState(new Date());
+ useEffect(() => {
+ setNow(new Date());
+ }, [pollLogsState]);
const replicaLog = pollLogsState && pollLogsState.data;
+ const selectedReplica = replica;
return (
@@ -76,15 +76,38 @@ const PageReplica = (props) => {
Replica {smallReplicaName(replicaName)} ,
component {componentName}
+ {selectedReplica && (
+
+
+ Created{' '}
+
+
+
+
+
+ Duration{' '}
+
+
+
+
+
+ )}
- Status
+ Status
- {replicaStatus && replicaStatus.status !== STATUS_OK && (
+ {selectedReplica && selectedReplica.status !== STATUS_OK && (
Status message is:
- {replica.statusMessage}
+ {selectedReplica.statusMessage}
)}
+ {selectedReplica &&
+ selectedReplica.restartCount != NaN &&
+ selectedReplica.restartCount > 0 && (
+
Restarted {selectedReplica.restartCount} times
+ )}
Log
{replicaLog && {replicaLog}
}
@@ -107,6 +130,6 @@ PageReplica.propTypes = {
};
export default mapRouteParamsToProps(
- ['appName', 'envName', 'deploymentName', 'componentName', 'replicaName'],
+ ['appName', 'envName', 'componentName', 'replicaName'],
PageReplica
);
diff --git a/src/components/page-replica/use-poll-logs.js b/src/components/page-replica/use-poll-logs.js
index 02d0f8bee..34a11490c 100644
--- a/src/components/page-replica/use-poll-logs.js
+++ b/src/components/page-replica/use-poll-logs.js
@@ -1,11 +1,11 @@
import { usePollingPlain } from '../../effects';
-const usePollLogs = (appName, deploymentName, componentName, replicaName) => {
+const usePollLogs = (appName, envName, componentName, replicaName) => {
const encAppName = encodeURIComponent(appName);
- const encDeployName = encodeURIComponent(deploymentName);
+ const encEnvName = encodeURIComponent(envName);
const encComponentName = encodeURIComponent(componentName);
const encReplicaName = encodeURIComponent(replicaName);
- const path = `/applications/${encAppName}/deployments/${encDeployName}/components/${encComponentName}/replicas/${encReplicaName}/logs`;
+ const path = `/applications/${encAppName}/environments/${encEnvName}/components/${encComponentName}/replicas/${encReplicaName}/logs`;
return usePollingPlain(path, 5000);
};
diff --git a/src/components/page-replica/use-select-replica.js b/src/components/page-replica/use-select-replica.js
index a8b4460f0..6e8bc03b9 100644
--- a/src/components/page-replica/use-select-replica.js
+++ b/src/components/page-replica/use-select-replica.js
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
+import replicaSummaryNormaliser from '../../models/replica-summary/normaliser';
const useSelectReplica = (environment, componentName, replicaName) => {
const [replica, setReplica] = useState();
@@ -15,7 +16,7 @@ const useSelectReplica = (environment, componentName, replicaName) => {
component && component.replicaList
? component.replicaList.find((replica) => replica.name === replicaName)
: null;
- setReplica(selectedReplica);
+ setReplica(replicaSummaryNormaliser(selectedReplica));
}, [environment, componentName, replicaName]);
return replica;
diff --git a/src/components/page-scheduled-job/index.js b/src/components/page-scheduled-job/index.js
new file mode 100644
index 000000000..427544d20
--- /dev/null
+++ b/src/components/page-scheduled-job/index.js
@@ -0,0 +1,143 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import useGetEnvironment from '../page-environment/use-get-environment';
+import usePollLogs from './use-poll-logs';
+import useSelectScheduledJob from './use-select-scheduled-job';
+
+import Breadcrumb from '../breadcrumb';
+import Code from '../code';
+import EnvironmentBadge from '../environment-badge';
+import ScheduledJobStatus from '../scheduled-job-status';
+import AsyncResource from '../async-resource/simple-async-resource';
+
+import routes from '../../routes';
+import { mapRouteParamsToProps } from '../../utils/routing';
+import { routeWithParams, smallScheduledJobName } from '../../utils/string';
+import * as routing from '../../utils/routing';
+import RelativeToNow from '../time/relative-to-now';
+import Duration from '../time/duration';
+
+const PageScheduledJob = (props) => {
+ const { appName, envName, jobComponentName, scheduledJobName } = props;
+
+ const [getEnvironmentState] = useGetEnvironment(appName, envName);
+ const [pollLogsState] = usePollLogs(
+ appName,
+ envName,
+ jobComponentName,
+ scheduledJobName
+ );
+ const scheduledJob = useSelectScheduledJob(
+ getEnvironmentState.data,
+ jobComponentName,
+ scheduledJobName
+ );
+ const scheduledJobStatus = scheduledJob ? scheduledJob.status : null;
+ const scheduledJobLog = pollLogsState && pollLogsState.data;
+
+ return (
+
+ ,
+ to: routeWithParams(routes.appEnvironment, {
+ appName,
+ envName,
+ }),
+ },
+ {
+ to: routeWithParams(routes.appActiveJobComponent, {
+ appName,
+ envName,
+ jobComponentName,
+ }),
+ label: jobComponentName,
+ },
+ { label: smallScheduledJobName(scheduledJobName) },
+ ]}
+ />
+
+
+
+
+
+ Overview
+
+ Scheduled job{' '}
+ {smallScheduledJobName(scheduledJobName)} ,
+ job {jobComponentName}
+
+ {scheduledJob && (
+
+
+ Created{' '}
+
+
+
+
+
+ Started{' '}
+
+
+
+
+
+ Ended{' '}
+
+
+
+
+
+ Duration{' '}
+
+
+
+
+
+ )}
+
+ Status
+
+ {scheduledJobLog && (
+
+
Log
+
+ {scheduledJobLog && {scheduledJobLog}
}
+
+
+ )}
+ {!scheduledJobLog && No logs
}
+
+
+
+
+
+
+ );
+};
+
+PageScheduledJob.propTypes = {
+ appName: PropTypes.string.isRequired,
+ jobComponentName: PropTypes.string.isRequired,
+ deploymentName: PropTypes.string,
+ envName: PropTypes.string.isRequired,
+ scheduledJobName: PropTypes.string.isRequired,
+};
+
+export default mapRouteParamsToProps(
+ ['appName', 'envName', 'jobComponentName', 'scheduledJobName'],
+ PageScheduledJob
+);
diff --git a/src/components/page-scheduled-job/use-poll-logs.js b/src/components/page-scheduled-job/use-poll-logs.js
new file mode 100644
index 000000000..42bb0d600
--- /dev/null
+++ b/src/components/page-scheduled-job/use-poll-logs.js
@@ -0,0 +1,13 @@
+import { usePollingPlain } from '../../effects';
+
+const usePollLogs = (appName, envName, jobComponentName, scheduledJobName) => {
+ const encAppName = encodeURIComponent(appName);
+ const encEnvName = encodeURIComponent(envName);
+ const encJobComponentName = encodeURIComponent(jobComponentName);
+ const encScheduledJobName = encodeURIComponent(scheduledJobName);
+ const path = `/applications/${encAppName}/environments/${encEnvName}/jobcomponents/${encJobComponentName}/scheduledjobs/${encScheduledJobName}/logs`;
+
+ return usePollingPlain(path, 5000);
+};
+
+export default usePollLogs;
diff --git a/src/components/page-scheduled-job/use-select-scheduled-job.js b/src/components/page-scheduled-job/use-select-scheduled-job.js
new file mode 100644
index 000000000..a62604cac
--- /dev/null
+++ b/src/components/page-scheduled-job/use-select-scheduled-job.js
@@ -0,0 +1,31 @@
+import { useState, useEffect } from 'react';
+import scheduledJobSummaryNormaliser from '../../models/scheduled-job-summary/normaliser';
+
+const useSelectScheduledJob = (
+ environment,
+ jobComponentName,
+ scheduledJobName
+) => {
+ const [scheduledJob, setScheduledJob] = useState();
+
+ useEffect(() => {
+ const deployment = environment ? environment.activeDeployment : null;
+
+ const component =
+ deployment && deployment.components
+ ? deployment.components.find((comp) => comp.name === jobComponentName)
+ : null;
+
+ const selectedScheduledJob =
+ component && component.scheduledJobList
+ ? component.scheduledJobList.find(
+ (scheduledJob) => scheduledJob.name === scheduledJobName
+ )
+ : null;
+ setScheduledJob(scheduledJobSummaryNormaliser(selectedScheduledJob));
+ }, [environment, jobComponentName, scheduledJobName]);
+
+ return scheduledJob;
+};
+
+export default useSelectScheduledJob;
diff --git a/src/components/page-step/index.js b/src/components/page-step/index.js
index 298fcf9fa..bd117abd5 100644
--- a/src/components/page-step/index.js
+++ b/src/components/page-step/index.js
@@ -53,7 +53,10 @@ export class PageStep extends React.Component {
{
+ if (status === STATUS_FAIL) {
+ return (
+
+ Failing
+
+ );
+ }
+
+ return {status} ;
+};
+
+export default ScheduledJobStatus;
diff --git a/src/components/time/duration.js b/src/components/time/duration.js
index 2875cc388..06dbadbf6 100644
--- a/src/components/time/duration.js
+++ b/src/components/time/duration.js
@@ -6,7 +6,9 @@ export const Duration = ({ start, end, title }) => {
if (!end) {
return null;
}
-
+ if (end < start) {
+ end = start;
+ }
return {differenceInWords(end, start)} ;
};
diff --git a/src/models/component-summary/index.js b/src/models/component-summary/index.js
index f98ef12a8..715e5a5b3 100644
--- a/src/models/component-summary/index.js
+++ b/src/models/component-summary/index.js
@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
+import ComponentType from '../component-type';
export default Object.freeze({
image: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
+ type: ComponentType.isRequired,
});
diff --git a/src/models/component-summary/test-data.js b/src/models/component-summary/test-data.js
index ddeeb2d3d..36802a846 100644
--- a/src/models/component-summary/test-data.js
+++ b/src/models/component-summary/test-data.js
@@ -3,11 +3,13 @@ export const testData = [
__testDescription: 'Normal component',
name: 'component-a',
image: 'an-image',
+ type: 'component',
},
{
__testDescription: 'Missing image',
__testIsInvalidSample: true,
name: 'component-b',
+ type: 'component',
},
];
diff --git a/src/models/component-type/index.js b/src/models/component-type/index.js
new file mode 100644
index 000000000..e545ce828
--- /dev/null
+++ b/src/models/component-type/index.js
@@ -0,0 +1,28 @@
+import PropTypes from 'prop-types';
+
+export default PropTypes.oneOf(['component', 'job']);
+
+export const componentType = {
+ component: 'component',
+ job: 'job',
+};
+
+const componentTypeLabel = { component: 'Component', job: 'Job' };
+const componentTypeLabelPlural = { component: 'Components', job: 'Jobs' };
+
+export const buildComponentTypeLabelMap = (type) => {
+ return componentTypeLabel['' + type];
+};
+
+export const buildComponentTypeLabelPluralMap = (type) => {
+ return componentTypeLabelPlural['' + type];
+};
+
+export const buildComponentMap = (components) => {
+ return components.reduce((componentMap, component) => {
+ let key = component.type;
+ componentMap[key] = componentMap[key] || [];
+ componentMap[key].push(component);
+ return componentMap;
+ }, {});
+};
diff --git a/src/models/component-type/normaliser.js b/src/models/component-type/normaliser.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/models/component/index.js b/src/models/component/index.js
index dc2214b68..91ab05568 100644
--- a/src/models/component/index.js
+++ b/src/models/component/index.js
@@ -2,13 +2,21 @@ import PropTypes from 'prop-types';
import PortModel from '../port';
import ReplicaSummaryModel from '../replica-summary';
+import ScheduledJobSummaryModel from '../scheduled-job-summary';
+import ComponentType from '../component-type';
export default Object.freeze({
image: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
+ type: ComponentType.isRequired,
status: PropTypes.string.isRequired,
ports: PropTypes.arrayOf(PropTypes.exact(PortModel)),
+ schedulerPort: PropTypes.string,
+ scheduledJobPayloadPath: PropTypes.string,
replicaList: PropTypes.arrayOf(PropTypes.exact(ReplicaSummaryModel)),
+ scheduledJobList: PropTypes.arrayOf(
+ PropTypes.exact(ScheduledJobSummaryModel)
+ ),
secrets: PropTypes.arrayOf(PropTypes.string),
variables: PropTypes.objectOf(PropTypes.string),
horizontalScalingSummary: PropTypes.objectOf(PropTypes.number),
diff --git a/src/models/component/normaliser.js b/src/models/component/normaliser.js
index 964e8513a..c8571f469 100644
--- a/src/models/component/normaliser.js
+++ b/src/models/component/normaliser.js
@@ -2,6 +2,7 @@ import pick from 'lodash/pick';
import portNormaliser from '../port/normaliser';
import replicaSummaryNormaliser from '../replica-summary/normaliser';
+import scheduledJobSummaryNormaliser from '../scheduled-job-summary/normaliser';
import model from '.';
@@ -19,6 +20,10 @@ export const normaliser = (props) => {
? component.replicaList.map(replicaSummaryNormaliser)
: null;
+ component.scheduledJobList = component.scheduledJobList
+ ? component.scheduledJobList.map(scheduledJobSummaryNormaliser)
+ : null;
+
return Object.freeze(component);
};
diff --git a/src/models/job-summary/normaliser.js b/src/models/job-summary/normaliser.js
index a5c916362..d6313134f 100644
--- a/src/models/job-summary/normaliser.js
+++ b/src/models/job-summary/normaliser.js
@@ -3,7 +3,7 @@ import pick from 'lodash/pick';
import model from '.';
/**
- * Create a Job Summary object
+ * Create a Pipeline Job Summary object
*/
export const normaliser = (props) => {
const jobSummary = pick(props, Object.keys(model));
diff --git a/src/models/job/test-data.js b/src/models/job/test-data.js
index f638dd026..1cb3c8f71 100644
--- a/src/models/job/test-data.js
+++ b/src/models/job/test-data.js
@@ -14,10 +14,12 @@ const normalComponents = Object.freeze([
{
name: 'component-a',
image: 'an-image',
+ type: 'component',
},
{
name: 'component-b',
image: 'another-image',
+ type: 'component',
},
]);
diff --git a/src/models/replica-summary/index.js b/src/models/replica-summary/index.js
index 9642d48c9..6799a31a9 100644
--- a/src/models/replica-summary/index.js
+++ b/src/models/replica-summary/index.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
export default Object.freeze({
name: PropTypes.string.isRequired,
+ created: PropTypes.instanceOf(Date).isRequired,
status: PropTypes.oneOf([
'Pending',
'Failing',
@@ -9,5 +10,6 @@ export default Object.freeze({
'Terminated',
'Starting',
]).isRequired,
+ restartCount: PropTypes.number,
statusMessage: PropTypes.string,
});
diff --git a/src/models/replica-summary/normaliser.js b/src/models/replica-summary/normaliser.js
index 7ace1737e..bc245b6b1 100644
--- a/src/models/replica-summary/normaliser.js
+++ b/src/models/replica-summary/normaliser.js
@@ -6,9 +6,15 @@ import model from '.';
* Create a Replica object
*/
export const normaliser = (props) => {
+ if (props == null) {
+ return null;
+ }
const replica = pick(props, Object.keys(model));
replica.status = props.replicaStatus.status;
+ replica.created = replica.created ? new Date(replica.created) : null;
+ let restartCount = parseInt(replica.restartCount);
+ replica.restartCount = restartCount === NaN ? 0 : restartCount;
return Object.freeze(replica);
};
diff --git a/src/models/replica-summary/test-data.js b/src/models/replica-summary/test-data.js
index aeb294011..44d183528 100644
--- a/src/models/replica-summary/test-data.js
+++ b/src/models/replica-summary/test-data.js
@@ -3,23 +3,27 @@ export const testData = [
__testDescription: 'Running',
name: 'a-replica',
replicaStatus: { status: 'Running' },
+ created: '2018-11-19T14:31:23Z',
},
{
__testDescription: 'Starting',
name: 'b-replica',
replicaStatus: { status: 'Pending' },
+ created: '2018-11-19T14:31:23Z',
},
{
__testDescription: 'Failing',
name: 'c-replica',
replicaStatus: { status: 'Failing' },
statusMessage: 'Some error message',
+ created: '2018-11-19T14:31:23Z',
},
{
__testDescription: 'Wrong status',
__testIsInvalidSample: true,
name: 'd-replica',
replicaStatus: { status: 'Waiting' },
+ created: '2018-11-19T14:31:23Z',
},
];
diff --git a/src/models/scheduled-job-summary/index.js b/src/models/scheduled-job-summary/index.js
new file mode 100644
index 000000000..d5f313e62
--- /dev/null
+++ b/src/models/scheduled-job-summary/index.js
@@ -0,0 +1,12 @@
+import PropTypes from 'prop-types';
+import ProgressStatusModel from '../progress-status';
+import ReplicaSummaryModel from '../replica-summary';
+
+export default Object.freeze({
+ created: PropTypes.instanceOf(Date),
+ ended: PropTypes.instanceOf(Date),
+ name: PropTypes.string.isRequired,
+ started: PropTypes.instanceOf(Date),
+ status: ProgressStatusModel.isRequired,
+ replicaList: PropTypes.arrayOf(PropTypes.exact(ReplicaSummaryModel)),
+});
diff --git a/src/models/scheduled-job-summary/normaliser.js b/src/models/scheduled-job-summary/normaliser.js
new file mode 100644
index 000000000..d6313134f
--- /dev/null
+++ b/src/models/scheduled-job-summary/normaliser.js
@@ -0,0 +1,18 @@
+import pick from 'lodash/pick';
+
+import model from '.';
+
+/**
+ * Create a Pipeline Job Summary object
+ */
+export const normaliser = (props) => {
+ const jobSummary = pick(props, Object.keys(model));
+
+ jobSummary.started = jobSummary.started ? new Date(jobSummary.started) : null;
+ jobSummary.ended = jobSummary.ended ? new Date(jobSummary.ended) : null;
+ jobSummary.created = jobSummary.created ? new Date(jobSummary.created) : null;
+
+ return Object.freeze(jobSummary);
+};
+
+export default normaliser;
diff --git a/src/models/scheduled-job-summary/test-data.js b/src/models/scheduled-job-summary/test-data.js
new file mode 100644
index 000000000..9acd3e536
--- /dev/null
+++ b/src/models/scheduled-job-summary/test-data.js
@@ -0,0 +1,46 @@
+const normalReplicas = Object.freeze([
+ {
+ __testDescription: 'Running',
+ name: 'a-replica',
+ replicaStatus: { status: 'Running' },
+ },
+ {
+ __testDescription: 'Starting',
+ name: 'b-replica',
+ replicaStatus: { status: 'Pending' },
+ },
+ {
+ __testDescription: 'Failing',
+ name: 'c-replica',
+ replicaStatus: { status: 'Failing' },
+ statusMessage: 'Some error message',
+ },
+ {
+ __testDescription: 'Wrong status',
+ __testIsInvalidSample: true,
+ name: 'd-replica',
+ replicaStatus: { status: 'Waiting' },
+ },
+]);
+
+export const testData = [
+ {
+ __testDescription: 'Valid Running',
+ name: 'A Job',
+ created: '2018-11-19T14:31:23Z',
+ ended: '2018-11-19T14:32:23Z',
+ started: '2018-11-19T14:31:23Z',
+ status: 'Running',
+ replicaList: normalReplicas,
+ },
+ {
+ __testDescription: 'Valid Running not ended',
+ name: 'A Job',
+ created: '2018-11-19T14:31:23Z',
+ started: '2018-11-19T14:31:23Z',
+ status: 'Running',
+ replicaList: normalReplicas,
+ },
+];
+
+export default testData;
diff --git a/src/routes.js b/src/routes.js
index 8ff50d667..8a700f142 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -13,10 +13,12 @@ export const routes = {
appPrivateImageHub: '/applications/:appName/config/imagehubs/:imageHubName',
appBuildSecret: '/applications/:appName/config/buildsecrets/:secretName',
appComponent: '/applications/:appName/deployments/:deploymentName/component/:componentName',
+ appJobComponent: '/applications/:appName/deployments/:deploymentName/jobcomponent/:jobComponentName',
appDeployment: '/applications/:appName/deployments/:deploymentName',
appDeployments: '/applications/:appName/deployments',
appEnvironment: '/applications/:appName/envs/:envName',
appActiveComponent: '/applications/:appName/envs/:envName/component/:componentName',
+ appActiveJobComponent: '/applications/:appName/envs/:envName/jobcomponent/:jobComponentName',
appEnvComponent: '/applications/:appName/envs/:envName/component/:componentName',
appEnvironments: '/applications/:appName/envs',
appJob: '/applications/:appName/jobs/view/:jobName',
@@ -25,6 +27,7 @@ export const routes = {
appJobStep: '/applications/:appName/jobs/view/:jobName/steps/:stepName',
appPod: '/applications/:appName/envs/:envName/component/:componentName/pod/:podName',
appReplica: '/applications/:appName/envs/:envName/component/:componentName/replica/:replicaName',
+ appScheduledJob: '/applications/:appName/envs/:envName/jobcomponent/:jobComponentName/scheduledjob/:scheduledJobName',
appSecret: '/applications/:appName/envs/:envName/component/:componentName/secret/:secretName',
appCreate: '/applications/new',
diff --git a/src/state/environment/index.js b/src/state/environment/index.js
index efd852536..a36fc5844 100644
--- a/src/state/environment/index.js
+++ b/src/state/environment/index.js
@@ -119,9 +119,7 @@ export const getActiveDeploymentName = (state) => {
return env.activeDeployment.name;
};
-export const getSecret = (state, componentName, secretName) => {
- const env = getEnvironment(state);
-
+export const getComponentSecret = (env, secretName, componentName) => {
if (!env || !env.activeDeployment) {
return null;
}
@@ -130,3 +128,7 @@ export const getSecret = (state, componentName, secretName) => {
(secret) => secret.name === secretName && secret.component === componentName
);
};
+
+export const getSecret = (state, componentName, secretName) => {
+ return getComponentSecret(getEnvironment(state), secretName, componentName);
+};
diff --git a/src/utils/routing.js b/src/utils/routing.js
index c26fbfb14..9a08b3d73 100644
--- a/src/utils/routing.js
+++ b/src/utils/routing.js
@@ -18,7 +18,7 @@ import routes from '../routes';
* @todo Support object (key => function) as paramsToMap (like mapStateToProps)
*
* @param {string[]} paramsToMap List of URL parameters to inject as props
- * @param {React.Component} Component Component to receive props
+ * @param {function(*)} Component Component to receive props
*/
export function mapRouteParamsToProps(paramsToMap, Component) {
return withRouter((props) => {
@@ -80,6 +80,13 @@ export const getActiveComponentUrl = (appName, envName, componentName) =>
componentName,
});
+export const getActiveJobComponentUrl = (appName, envName, jobComponentName) =>
+ routeWithParams(routes.appActiveJobComponent, {
+ appName,
+ envName,
+ jobComponentName,
+ });
+
export const getReplicaUrl = (appName, envName, componentName, replicaName) =>
routeWithParams(routes.appReplica, {
appName,
@@ -88,6 +95,19 @@ export const getReplicaUrl = (appName, envName, componentName, replicaName) =>
replicaName,
});
+export const getScheduledJobUrl = (
+ appName,
+ envName,
+ jobComponentName,
+ scheduledJobName
+) =>
+ routeWithParams(routes.appScheduledJob, {
+ appName,
+ envName,
+ jobComponentName,
+ scheduledJobName,
+ });
+
export const getSecretUrl = (appName, envName, componentName, secretName) =>
routeWithParams(routes.appSecret, {
appName,
diff --git a/src/utils/string.js b/src/utils/string.js
index 144de4b0d..444ef14ae 100644
--- a/src/utils/string.js
+++ b/src/utils/string.js
@@ -69,3 +69,5 @@ export const smallDeploymentName = (() => {
export const smallJobName = (jobName) => jobName.slice(-5);
export const smallReplicaName = (replicaName) => replicaName.slice(-5);
+
+export const smallScheduledJobName = (scheduledJob) => scheduledJob.slice(-8);