Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic cockpit link resolution via well known endpoint #4297

Merged
merged 20 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2eeae23
chore(cockpit-links) refactor into a common react
AlexanderSkrock May 18, 2024
55e9e93
feat(well-known-api) dynamic webapp url resolution
AlexanderSkrock May 21, 2024
3a884db
fix(cockpit-links): issues with test cases
AlexanderSkrock Jul 11, 2024
9d27ce9
chore(cockpit-links): additional test cases for default handling
AlexanderSkrock Jul 11, 2024
e4be694
fix(wellknown-api): aligned field names
AlexanderSkrock Jul 11, 2024
53f03b3
chore(rest-api): extracted duplicate code
AlexanderSkrock Jul 11, 2024
81d896f
chore(cockpit-link): fix linting
AlexanderSkrock Jul 12, 2024
15d942a
test(client): use constants for status codes
AlexanderSkrock Jul 12, 2024
5788cba
chore(validator): fix linting
AlexanderSkrock Aug 2, 2024
3b47010
chore(tests) align test case with basic auth valiation
AlexanderSkrock Aug 11, 2024
80101e4
chore(cockpit-link): remove unnecessary useMemo usage
AlexanderSkrock Aug 12, 2024
4442452
chore(cockpit-link): remove unnecessary useMemo usage
AlexanderSkrock Aug 12, 2024
a08e5cb
chore(logging): use debug library instead of console.debug
AlexanderSkrock Aug 12, 2024
39a42a3
chore(cockpit-link): remove unnecessary useMemo usage
AlexanderSkrock Aug 12, 2024
ddc6529
chore(cockpit-link): remove unnecessary useMemo usage
AlexanderSkrock Aug 12, 2024
8a2a0b9
chore(tests): migrate single testcase to react testing library
AlexanderSkrock Aug 22, 2024
165e746
chore(tests): migrate the remaining testcases to react testing library
AlexanderSkrock Aug 22, 2024
57cd89f
test: apply suggestions from code review
barmac Sep 20, 2024
92ef9a4
test: fix syntax errors
barmac Sep 20, 2024
9f3108e
tests(deployment-overlay): fix submit simulation
AlexanderSkrock Sep 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
Formik,
Field
} from 'formik';
import { GenericApiErrors } from '../shared/RestAPI';

export default class DeploymentConfigOverlay extends React.PureComponent {

Expand Down Expand Up @@ -153,7 +154,7 @@ export default class DeploymentConfigOverlay extends React.PureComponent {
code
} = connectionValidation;

if (code === 'UNAUTHORIZED') {
if (code === GenericApiErrors.UNAUTHORIZED) {
this.setState({
isAuthNeeded: true
});
Expand Down Expand Up @@ -213,7 +214,7 @@ export default class DeploymentConfigOverlay extends React.PureComponent {
if (!result) {
this.onAuthDetection(false);
} else if (!result.isExpired) {
this.onAuthDetection(!!result && (result.code === 'UNAUTHORIZED'));
this.onAuthDetection(!!result && (result.code === GenericApiErrors.UNAUTHORIZED));
}
});
};
Expand Down
66 changes: 16 additions & 50 deletions client/src/plugins/camunda-plugin/deployment-tool/DeploymentTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import { omit } from 'min-dash';

import classNames from 'classnames';

import { default as CamundaAPI, ApiErrors, ConnectionError } from '../shared/CamundaAPI';
import { default as CamundaAPI, DeploymentError } from '../shared/CamundaAPI';
import { ConnectionError, GenericApiErrors } from '../shared/RestAPI';
import AUTH_TYPES from '../shared/AuthTypes';

import CockpitDeploymentLink from '../shared/ui/CockpitDeploymentLink';

import DeploymentConfigOverlay from './DeploymentConfigOverlay';
import DeploymentConfigValidator from './validation/DeploymentConfigValidator';
import { DeploymentError } from '../shared/CamundaAPI';

import * as css from './DeploymentTool.less';

Expand All @@ -32,6 +34,7 @@ import { Fill } from '../../../app/slot-fill';
import DeployIcon from 'icons/Deploy.svg';

import { ENGINES } from '../../../util/Engines';
import { determineCockpitUrl } from '../shared/webAppUrls';

const DEPLOYMENT_DETAILS_CONFIG_KEY = 'deployment-tool';
const ENGINE_ENDPOINTS_CONFIG_KEY = 'camundaEngineEndpoints';
Expand Down Expand Up @@ -175,7 +178,7 @@ export default class DeploymentTool extends PureComponent {
}
}

handleDeploymentSuccess(tab, deployment, version, configuration) {
async handleDeploymentSuccess(tab, deployment, version, configuration) {
const {
displayNotification,
triggerAction
Expand All @@ -189,10 +192,12 @@ export default class DeploymentTool extends PureComponent {
url
} = endpoint;

const cockpitUrl = await this.getCockpitUrl(url);

displayNotification({
type: 'success',
title: `${getDeploymentType(tab)} deployed`,
content: <CockpitLink endpointUrl={ url } deployment={ deployment } />,
content: <CockpitDeploymentLink cockpitUrl={ cockpitUrl } deployment={ deployment } />,
duration: 8000
});

Expand All @@ -211,6 +216,10 @@ export default class DeploymentTool extends PureComponent {
});
}

async getCockpitUrl(engineUrl) {
return await determineCockpitUrl(engineUrl);
}

async saveProcessDefinition(tab, deployment) {

if (!deployment || !deployment.deployedProcessDefinition) {
Expand Down Expand Up @@ -533,9 +542,9 @@ export default class DeploymentTool extends PureComponent {

const { code } = result;

return (code !== ApiErrors.NO_INTERNET_CONNECTION &&
code !== ApiErrors.CONNECTION_FAILED &&
code !== ApiErrors.NOT_FOUND);
return (code !== GenericApiErrors.NO_INTERNET_CONNECTION &&
code !== GenericApiErrors.CONNECTION_FAILED &&
code !== GenericApiErrors.NOT_FOUND);
}

closeOverlay(overlayState) {
Expand Down Expand Up @@ -597,39 +606,6 @@ export default class DeploymentTool extends PureComponent {

}

function CockpitLink(props) {
const {
endpointUrl,
deployment
} = props;

const {
id,
deployedProcessDefinition
} = deployment;

const baseUrl = getWebAppsBaseUrl(endpointUrl);

const query = `deploymentsQuery=%5B%7B%22type%22:%22id%22,%22operator%22:%22eq%22,%22value%22:%22${id}%22%7D%5D`;
const cockpitUrl = `${baseUrl}/cockpit/default/#/repository/?${query}`;

return (
<div className={ css.CockpitLink }>
{deployedProcessDefinition ?
(
<div>
Process definition ID:
<code>{deployedProcessDefinition.id} </code>
</div>
)
: null}
<a href={ cockpitUrl }>
Open in Camunda Cockpit
</a>
</div>
);
}

// helpers //////////

function withoutExtension(name) {
Expand Down Expand Up @@ -697,13 +673,3 @@ function withSerializedAttachments(deployment) {
function basename(filePath) {
return filePath.split('\\').pop().split('/').pop();
}

function getWebAppsBaseUrl(url) {
const [ protocol,, host, restRoot ] = url.split('/');

return isTomcat(restRoot) ? `${protocol}//${host}/camunda/app` : `${protocol}//${host}/app`;
}

function isTomcat(restRoot) {
return restRoot === 'engine-rest';
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,4 @@
height: 24px;
fill: var(--status-bar-icon-font-color);
}
}

:local(.CockpitLink) {

& > div {
margin-bottom: 5px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import React from 'react';

import { act } from 'react-dom/test-utils';
import { waitFor } from '@testing-library/react';

import {
mount,
Expand All @@ -24,6 +24,7 @@ import { merge } from 'min-dash';
import AUTH_TYPES from '../../shared/AuthTypes';
import DeploymentConfigOverlay from '../DeploymentConfigOverlay';
import DeploymentConfigValidator from '../validation/DeploymentConfigValidator';
import { GenericApiErrors } from '../../shared/RestAPI';

let mounted;

Expand Down Expand Up @@ -75,7 +76,7 @@ describe('<DeploymentConfigOverlay>', () => {
});


it('should display hint if the username and password are missing when submitting', (done) => {
it('should display hint if the username and password are missing when submitting', async () => {

// given
const configuration = {
Expand All @@ -92,7 +93,7 @@ describe('<DeploymentConfigOverlay>', () => {
const validator = new MockValidator({
validateConnection: () => new Promise((resolve, err) => {
resolve({
code: 'UNAUTHORIZED'
code: GenericApiErrors.UNAUTHORIZED
});
})
});
Expand All @@ -106,7 +107,6 @@ describe('<DeploymentConfigOverlay>', () => {
validator
}, mount);

// when
setTimeout(() => {

// delayed execution because it is async that the deployment
Expand All @@ -116,15 +116,14 @@ describe('<DeploymentConfigOverlay>', () => {
});

// then
setTimeout(() => {
await waitFor(() => {
wrapper.update();
expect(wrapper.find('.invalid-feedback')).to.have.length(2);
done();
}, 200);
});
});


it('should display hint if token is missing', (done) => {
it('should display hint if token is missing', async () => {

// given
const configuration = {
Expand All @@ -138,43 +137,39 @@ describe('<DeploymentConfigOverlay>', () => {
}
};

const validator = new MockValidator({
validateConnection: () => Promise.resolve({
code: GenericApiErrors.UNAUTHORIZED
})
});

const {
wrapper,
instance
} = createOverlay({
anchor,
configuration
configuration,
validator
}, mount);

// when
act(() => {
instance.setState({ isAuthNeeded: true });
});

instance.isOnBeforeSubmit = true;

wrapper.update();
setTimeout(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that the current test suite for the deployment tool is in bad shape, but I'd rather expect us to properly make use of act and waitFor instead of introducing more setTimeout calls. Let's not make the tests depend on the time.

Copy link
Contributor Author

@AlexanderSkrock AlexanderSkrock Aug 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about the Formik workaround with the isOnBeforeSubmit flag which seems to stop working as soon as I replace setTimeout.

Copy link
Contributor Author

@AlexanderSkrock AlexanderSkrock Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I managed to get this working now, but I had to introduce a new dependency @testing-library/react so we have those calls available.


act(() => {
// delayed execution because it is async that the deployment
// tool knows if the authentication is necessary
instance.isOnBeforeSubmit = true;
wrapper.find('.btn-primary').simulate('submit');
});

// then
setTimeout(() => {
await waitFor(() => {
wrapper.update();

try {
expect(wrapper.find('.invalid-feedback')).to.have.length(1);
} catch (err) {
return done(err);
}

return done();
}, 100);
expect(wrapper.find('.invalid-feedback')).to.have.length(1);
});
});


it('should not display hint if the username and password are complete', (done) => {
it('should not display hint if the username and password are complete', async () => {

// given
const configuration = {
Expand Down Expand Up @@ -208,15 +203,14 @@ describe('<DeploymentConfigOverlay>', () => {
wrapper.find('.btn-primary').simulate('submit');

// then
setTimeout(() => {
await waitFor(() => {
wrapper.update();
expect(wrapper.find('.invalid-feedback')).to.have.length(0);
done();
});
});


it('should not disable deploy button when connection cannot be established', (done) => {
it('should not disable deploy button when connection cannot be established', async () => {

// given
const configuration = {
Expand Down Expand Up @@ -250,15 +244,14 @@ describe('<DeploymentConfigOverlay>', () => {
wrapper.find('.btn-primary').simulate('submit');

// then
setTimeout(() => {
await waitFor(() => {
wrapper.setProps({});
expect(wrapper.find('.btn-primary').props()).to.have.property('disabled', false);
done();
});
});


it('should hide username password fields if auth is not needed', (done) => {
it('should hide username password fields if auth is not needed', async () => {

// given
const configuration = {
Expand Down Expand Up @@ -287,16 +280,15 @@ describe('<DeploymentConfigOverlay>', () => {
}, mount);

// then
setTimeout(() => {
await waitFor(() => {
wrapper.update();
expect(wrapper.find('[id="endpoint.username"]')).to.have.length(0);
expect(wrapper.find('[id="endpoint.password"]')).to.have.length(0);
done();
});
});


it('should hide token field if auth is not needed', (done) => {
it('should hide token field if auth is not needed', async () => {

// given
const configuration = {
Expand Down Expand Up @@ -325,16 +317,15 @@ describe('<DeploymentConfigOverlay>', () => {
}, mount);

// then
setTimeout(() => {
await waitFor(() => {
wrapper.update();
expect(wrapper.find('[id="endpoint.token"]')).to.have.length(0);
done();
});
});
});


it('should not disable deploy button when form is invalid', (done) => {
it('should not disable deploy button when form is invalid', async () => {

// given
const configuration = {
Expand All @@ -359,10 +350,9 @@ describe('<DeploymentConfigOverlay>', () => {
wrapper.find('.btn-primary').simulate('click');

// then
setTimeout(() => {
await waitFor(() => {
wrapper.update();
expect(wrapper.find('.btn-primary').props()).to.have.property('disabled', false);
done();
});
});

Expand Down Expand Up @@ -400,7 +390,6 @@ describe('<DeploymentConfigOverlay>', () => {

// then
expect(saveCredentials).to.have.been.called;

});


Expand Down
Loading
Loading