Compare commits
1 Commits
aut-1180
...
stringify-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ab4abd590a |
@@ -16,6 +16,7 @@ import trimWhitespace from './transformers/trim-whitespace.js';
|
|||||||
import useDefaultValue from './transformers/use-default-value.js';
|
import useDefaultValue from './transformers/use-default-value.js';
|
||||||
import parseStringifiedJson from './transformers/parse-stringified-json.js';
|
import parseStringifiedJson from './transformers/parse-stringified-json.js';
|
||||||
import createUuid from './transformers/create-uuid.js';
|
import createUuid from './transformers/create-uuid.js';
|
||||||
|
import stringifyJson from './transformers/stringify-json.js';
|
||||||
|
|
||||||
const transformers = {
|
const transformers = {
|
||||||
base64ToString,
|
base64ToString,
|
||||||
@@ -34,6 +35,7 @@ const transformers = {
|
|||||||
useDefaultValue,
|
useDefaultValue,
|
||||||
parseStringifiedJson,
|
parseStringifiedJson,
|
||||||
createUuid,
|
createUuid,
|
||||||
|
stringifyJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineAction({
|
export default defineAction({
|
||||||
@@ -63,6 +65,7 @@ export default defineAction({
|
|||||||
{ label: 'Extract Number', value: 'extractNumber' },
|
{ label: 'Extract Number', value: 'extractNumber' },
|
||||||
{ label: 'Lowercase', value: 'lowercase' },
|
{ label: 'Lowercase', value: 'lowercase' },
|
||||||
{ label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
|
{ label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
|
||||||
|
{ label: 'Stringify JSON', value: 'stringifyJson' },
|
||||||
{ label: 'Pluralize', value: 'pluralize' },
|
{ label: 'Pluralize', value: 'pluralize' },
|
||||||
{ label: 'Replace', value: 'replace' },
|
{ label: 'Replace', value: 'replace' },
|
||||||
{ label: 'String to Base64', value: 'stringToBase64' },
|
{ label: 'String to Base64', value: 'stringToBase64' },
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
const stringifyJson = ($) => {
|
||||||
|
const input = $.step.parameters.input;
|
||||||
|
|
||||||
|
return JSON.stringify(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default stringifyJson;
|
@@ -13,6 +13,7 @@ import encodeUri from './text/encode-uri.js';
|
|||||||
import trimWhitespace from './text/trim-whitespace.js';
|
import trimWhitespace from './text/trim-whitespace.js';
|
||||||
import useDefaultValue from './text/use-default-value.js';
|
import useDefaultValue from './text/use-default-value.js';
|
||||||
import parseStringifiedJson from './text/parse-stringified-json.js';
|
import parseStringifiedJson from './text/parse-stringified-json.js';
|
||||||
|
import stringifyJson from './text/stringify-json.js';
|
||||||
import performMathOperation from './numbers/perform-math-operation.js';
|
import performMathOperation from './numbers/perform-math-operation.js';
|
||||||
import randomNumber from './numbers/random-number.js';
|
import randomNumber from './numbers/random-number.js';
|
||||||
import formatNumber from './numbers/format-number.js';
|
import formatNumber from './numbers/format-number.js';
|
||||||
@@ -40,6 +41,7 @@ const options = {
|
|||||||
formatPhoneNumber,
|
formatPhoneNumber,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
parseStringifiedJson,
|
parseStringifiedJson,
|
||||||
|
stringifyJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const useDefaultValue = [
|
const parseStringifiedJson = [
|
||||||
{
|
{
|
||||||
label: 'Input',
|
label: 'Input',
|
||||||
key: 'input',
|
key: 'input',
|
||||||
@@ -9,4 +9,4 @@ const useDefaultValue = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default useDefaultValue;
|
export default parseStringifiedJson;
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
const stringifyJson = [
|
||||||
|
{
|
||||||
|
label: 'Input',
|
||||||
|
key: 'input',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'JSON to stringify.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default stringifyJson;
|
@@ -1,12 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
await knex('config').insert({
|
|
||||||
key: 'userManagement.preventUsersFromUpdatingTheirProfile',
|
|
||||||
value: {
|
|
||||||
data: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
await knex('config').where({ key: 'userManagement.preventUsersFromUpdatingTheirProfile' }).delete();
|
|
||||||
};
|
|
@@ -2,137 +2,67 @@ import get from 'lodash.get';
|
|||||||
|
|
||||||
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[^.}{]+)+}})/g;
|
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[^.}{]+)+}})/g;
|
||||||
|
|
||||||
function getParameterEntries(parameters) {
|
export default function computeParameters(parameters, executionSteps) {
|
||||||
return Object.entries(parameters);
|
const entries = Object.entries(parameters);
|
||||||
}
|
return entries.reduce((result, [key, value]) => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parts = value.split(variableRegExp);
|
||||||
|
|
||||||
function computeParameterEntries(parameterEntries, executionSteps) {
|
const computedValue = parts
|
||||||
const defaultComputedParameters = {};
|
.map((part) => {
|
||||||
return parameterEntries.reduce((result, [key, value]) => {
|
const isVariable = part.match(variableRegExp);
|
||||||
const parameterComputedValue = computeParameter(value, executionSteps);
|
|
||||||
|
if (isVariable) {
|
||||||
|
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '');
|
||||||
|
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
|
||||||
|
const keyPath = keyPaths.join('.');
|
||||||
|
const executionStep = executionSteps.find((executionStep) => {
|
||||||
|
return executionStep.stepId === stepId;
|
||||||
|
});
|
||||||
|
const data = executionStep?.dataOut;
|
||||||
|
const dataValue = get(data, keyPath);
|
||||||
|
|
||||||
|
// Covers both arrays and objects
|
||||||
|
if (typeof dataValue === 'object') {
|
||||||
|
return JSON.stringify(dataValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return part;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// challenge the input to see if it is stringifies object or array
|
||||||
|
try {
|
||||||
|
const parsedValue = JSON.parse(computedValue);
|
||||||
|
|
||||||
|
if (typeof parsedValue === 'number') {
|
||||||
|
throw new Error('Use original unparsed value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
[key]: parsedValue,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
[key]: computedValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
[key]: value.map((item) => computeParameters(item, executionSteps)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
[key]: parameterComputedValue,
|
[key]: value,
|
||||||
}
|
};
|
||||||
}, defaultComputedParameters);
|
}, {});
|
||||||
}
|
|
||||||
|
|
||||||
function computeParameter(value, executionSteps) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const computedStringParameter = computeStringParameter(value, executionSteps);
|
|
||||||
return computedStringParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
const computedArrayParameter = computeArrayParameter(value, executionSteps);
|
|
||||||
return computedArrayParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function splitByVariable(stringValue) {
|
|
||||||
const parts = stringValue.split(variableRegExp);
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isVariable(stringValue) {
|
|
||||||
return stringValue.match(variableRegExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
function splitVariableByStepIdAndKeyPath(variableValue) {
|
|
||||||
const stepIdAndKeyPath = variableValue.replace(/{{step.|}}/g, '');
|
|
||||||
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
|
|
||||||
const keyPath = keyPaths.join('.');
|
|
||||||
|
|
||||||
return {
|
|
||||||
stepId,
|
|
||||||
keyPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVariableStepId(variableValue) {
|
|
||||||
const { stepId } = splitVariableByStepIdAndKeyPath(variableValue);
|
|
||||||
|
|
||||||
return stepId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVariableKeyPath(variableValue) {
|
|
||||||
const { keyPath } = splitVariableByStepIdAndKeyPath(variableValue);
|
|
||||||
|
|
||||||
return keyPath
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVariableExecutionStep(variableValue, executionSteps) {
|
|
||||||
const stepId = getVariableStepId(variableValue);
|
|
||||||
|
|
||||||
const executionStep = executionSteps.find((executionStep) => {
|
|
||||||
return executionStep.stepId === stepId;
|
|
||||||
});
|
|
||||||
|
|
||||||
return executionStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeVariable(variable, executionSteps) {
|
|
||||||
const keyPath = getVariableKeyPath(variable);
|
|
||||||
const executionStep = getVariableExecutionStep(variable, executionSteps);
|
|
||||||
const data = executionStep?.dataOut;
|
|
||||||
const computedVariable = get(data, keyPath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inline both arrays and objects. Otherwise, variables resolving to
|
|
||||||
* them would be resolved as `[object Object]` or lose their shape.
|
|
||||||
*/
|
|
||||||
if (typeof computedVariable === 'object') {
|
|
||||||
return JSON.stringify(computedVariable);
|
|
||||||
}
|
|
||||||
|
|
||||||
return computedVariable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoParseComputedVariable(computedVariable) {
|
|
||||||
// challenge the input to see if it is stringified object or array
|
|
||||||
try {
|
|
||||||
const parsedValue = JSON.parse(computedVariable);
|
|
||||||
|
|
||||||
if (typeof parsedValue === 'number') {
|
|
||||||
throw new Error('Use original unparsed value.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedValue;
|
|
||||||
} catch (error) {
|
|
||||||
return computedVariable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeStringParameter(stringValue, executionSteps) {
|
|
||||||
const parts = splitByVariable(stringValue);
|
|
||||||
|
|
||||||
const computedValue = parts
|
|
||||||
.map((part) => {
|
|
||||||
const variable = isVariable(part);
|
|
||||||
|
|
||||||
if (variable) {
|
|
||||||
return computeVariable(part, executionSteps);
|
|
||||||
}
|
|
||||||
|
|
||||||
return part;
|
|
||||||
})
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
const autoParsedValue = autoParseComputedVariable(computedValue);
|
|
||||||
|
|
||||||
return autoParsedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeArrayParameter(arrayValue, executionSteps) {
|
|
||||||
return arrayValue.map((item) => computeParameters(item, executionSteps));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function computeParameters(parameters, executionSteps) {
|
|
||||||
const parameterEntries = getParameterEntries(parameters);
|
|
||||||
|
|
||||||
return computeParameterEntries(parameterEntries, executionSteps);
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
const { AuthenticatedPage } = require('./authenticated-page');
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
const { expect } = require('@playwright/test');
|
|
||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
export class FlowEditorPage extends AuthenticatedPage {
|
export class FlowEditorPage extends AuthenticatedPage {
|
||||||
screenshotPath = '/flow-editor';
|
screenshotPath = '/flow-editor';
|
||||||
@@ -11,74 +9,17 @@ export class FlowEditorPage extends AuthenticatedPage {
|
|||||||
constructor(page) {
|
constructor(page) {
|
||||||
super(page);
|
super(page);
|
||||||
|
|
||||||
this.page = page;
|
|
||||||
this.appAutocomplete = this.page.getByTestId('choose-app-autocomplete');
|
this.appAutocomplete = this.page.getByTestId('choose-app-autocomplete');
|
||||||
this.eventAutocomplete = this.page.getByTestId('choose-event-autocomplete');
|
this.eventAutocomplete = this.page.getByTestId('choose-event-autocomplete');
|
||||||
this.continueButton = this.page.getByTestId('flow-substep-continue-button');
|
this.continueButton = this.page.getByTestId('flow-substep-continue-button');
|
||||||
this.testAndContinueButton = this.page.getByText('Test & Continue');
|
|
||||||
this.connectionAutocomplete = this.page.getByTestId(
|
this.connectionAutocomplete = this.page.getByTestId(
|
||||||
'choose-connection-autocomplete'
|
'choose-connection-autocomplete'
|
||||||
);
|
);
|
||||||
this.testOutput = this.page.getByTestId('flow-test-substep-output');
|
this.testOuput = this.page.getByTestId('flow-test-substep-output');
|
||||||
this.hasNoOutput = this.page.getByTestId('flow-test-substep-no-output');
|
|
||||||
this.unpublishFlowButton = this.page.getByTestId('unpublish-flow-button');
|
this.unpublishFlowButton = this.page.getByTestId('unpublish-flow-button');
|
||||||
this.publishFlowButton = this.page.getByTestId('publish-flow-button');
|
this.publishFlowButton = this.page.getByTestId('publish-flow-button');
|
||||||
this.infoSnackbar = this.page.getByTestId('flow-cannot-edit-info-snackbar');
|
this.infoSnackbar = this.page.getByTestId('flow-cannot-edit-info-snackbar');
|
||||||
this.trigger = this.page.getByLabel('Trigger on weekends?');
|
this.trigger = this.page.getByLabel('Trigger on weekends?');
|
||||||
this.stepCircularLoader = this.page.getByTestId('step-circular-loader');
|
this.stepCircularLoader = this.page.getByTestId('step-circular-loader');
|
||||||
this.flowName = this.page.getByTestId('editableTypography');
|
|
||||||
this.flowNameInput = this.page
|
|
||||||
.getByTestId('editableTypographyInput')
|
|
||||||
.locator('input');
|
|
||||||
}
|
|
||||||
|
|
||||||
async createWebhookTrigger(workSynchronously) {
|
|
||||||
await this.appAutocomplete.click();
|
|
||||||
await this.page.getByRole('option', { name: 'Webhook' }).click();
|
|
||||||
|
|
||||||
await expect(this.eventAutocomplete).toBeVisible();
|
|
||||||
await this.eventAutocomplete.click();
|
|
||||||
await this.page.getByRole('option', { name: 'Catch raw webhook' }).click();
|
|
||||||
await this.continueButton.click();
|
|
||||||
await this.page
|
|
||||||
.getByTestId('parameters.workSynchronously-autocomplete')
|
|
||||||
.click();
|
|
||||||
await this.page
|
|
||||||
.getByRole('option', { name: workSynchronously ? 'Yes' : 'No' })
|
|
||||||
.click();
|
|
||||||
await this.continueButton.click();
|
|
||||||
|
|
||||||
const webhookUrl = this.page.locator('input[name="webhookUrl"]');
|
|
||||||
if (workSynchronously) {
|
|
||||||
await expect(webhookUrl).toHaveValue(/sync/);
|
|
||||||
} else {
|
|
||||||
await expect(webhookUrl).not.toHaveValue(/sync/);
|
|
||||||
}
|
|
||||||
|
|
||||||
const triggerResponse = await axios.get(await webhookUrl.inputValue());
|
|
||||||
await expect(triggerResponse.status).toBe(204);
|
|
||||||
|
|
||||||
await expect(this.testOutput).not.toBeVisible();
|
|
||||||
await this.testAndContinueButton.click();
|
|
||||||
await expect(this.testOutput).toBeVisible();
|
|
||||||
await expect(this.hasNoOutput).not.toBeVisible();
|
|
||||||
await this.continueButton.click();
|
|
||||||
|
|
||||||
return await webhookUrl.inputValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
async chooseAppAndEvent(appName, eventName) {
|
|
||||||
await this.appAutocomplete.click();
|
|
||||||
await this.page.getByRole('option', { name: appName }).click();
|
|
||||||
await expect(this.eventAutocomplete).toBeVisible();
|
|
||||||
await this.eventAutocomplete.click();
|
|
||||||
await this.page.getByRole('option', { name: eventName }).click();
|
|
||||||
await this.continueButton.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async testAndContinue() {
|
|
||||||
await this.continueButton.last().click();
|
|
||||||
await expect(this.testOutput).toBeVisible();
|
|
||||||
await this.continueButton.click();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,82 +0,0 @@
|
|||||||
const { test, expect } = require('../../fixtures/index');
|
|
||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
test.describe('Webhook flow', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.getByTestId('create-flow-button').click();
|
|
||||||
await page.waitForURL(
|
|
||||||
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
|
||||||
);
|
|
||||||
await expect(page.getByTestId('flow-step')).toHaveCount(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Create a new flow with a sync Webhook step then a Webhook step', async ({
|
|
||||||
flowEditorPage,
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await flowEditorPage.flowName.click();
|
|
||||||
await flowEditorPage.flowNameInput.fill('syncWebhook');
|
|
||||||
const syncWebhookUrl = await flowEditorPage.createWebhookTrigger(true);
|
|
||||||
|
|
||||||
await flowEditorPage.chooseAppAndEvent('Webhook', 'Respond with');
|
|
||||||
|
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
|
||||||
|
|
||||||
await page
|
|
||||||
.getByTestId('parameters.statusCode-power-input')
|
|
||||||
.locator('[contenteditable]')
|
|
||||||
.fill('200');
|
|
||||||
await flowEditorPage.clickAway();
|
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
|
||||||
|
|
||||||
await page
|
|
||||||
.getByTestId('parameters.body-power-input')
|
|
||||||
.locator('[contenteditable]')
|
|
||||||
.fill('response from webhook');
|
|
||||||
await flowEditorPage.clickAway();
|
|
||||||
await expect(flowEditorPage.continueButton).toBeEnabled();
|
|
||||||
await flowEditorPage.continueButton.click();
|
|
||||||
|
|
||||||
await flowEditorPage.testAndContinue();
|
|
||||||
await flowEditorPage.publishFlowButton.click();
|
|
||||||
|
|
||||||
const response = await axios.get(syncWebhookUrl);
|
|
||||||
await expect(response.status).toBe(200);
|
|
||||||
await expect(response.data).toBe('response from webhook');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Create a new flow with an async Webhook step then a Webhook step', async ({
|
|
||||||
flowEditorPage,
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await flowEditorPage.flowName.click();
|
|
||||||
await flowEditorPage.flowNameInput.fill('asyncWebhook');
|
|
||||||
const asyncWebhookUrl = await flowEditorPage.createWebhookTrigger(false);
|
|
||||||
|
|
||||||
await flowEditorPage.chooseAppAndEvent('Webhook', 'Respond with');
|
|
||||||
|
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
|
||||||
|
|
||||||
await page
|
|
||||||
.getByTestId('parameters.statusCode-power-input')
|
|
||||||
.locator('[contenteditable]')
|
|
||||||
.fill('200');
|
|
||||||
await flowEditorPage.clickAway();
|
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
|
||||||
|
|
||||||
await page
|
|
||||||
.getByTestId('parameters.body-power-input')
|
|
||||||
.locator('[contenteditable]')
|
|
||||||
.fill('response from webhook');
|
|
||||||
await flowEditorPage.clickAway();
|
|
||||||
await expect(flowEditorPage.continueButton).toBeEnabled();
|
|
||||||
await flowEditorPage.continueButton.click();
|
|
||||||
|
|
||||||
await flowEditorPage.testAndContinue();
|
|
||||||
await flowEditorPage.publishFlowButton.click();
|
|
||||||
|
|
||||||
const response = await axios.get(asyncWebhookUrl);
|
|
||||||
await expect(response.status).toBe(204);
|
|
||||||
await expect(response.data).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
@@ -2,6 +2,7 @@ const { test, expect } = require('../../fixtures/index');
|
|||||||
|
|
||||||
test('Ensure creating a new flow works', async ({ page }) => {
|
test('Ensure creating a new flow works', async ({ page }) => {
|
||||||
await page.getByTestId('create-flow-button').click();
|
await page.getByTestId('create-flow-button').click();
|
||||||
|
await expect(page).toHaveURL(/\/editor\/create/);
|
||||||
await expect(page).toHaveURL(
|
await expect(page).toHaveURL(
|
||||||
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||||
);
|
);
|
||||||
@@ -68,9 +69,9 @@ test(
|
|||||||
|
|
||||||
await test.step('test trigger', async () => {
|
await test.step('test trigger', async () => {
|
||||||
await test.step('show sample output', async () => {
|
await test.step('show sample output', async () => {
|
||||||
await expect(flowEditorPage.testOutput).not.toBeVisible();
|
await expect(flowEditorPage.testOuput).not.toBeVisible();
|
||||||
await flowEditorPage.continueButton.click();
|
await flowEditorPage.continueButton.click();
|
||||||
await expect(flowEditorPage.testOutput).toBeVisible();
|
await expect(flowEditorPage.testOuput).toBeVisible();
|
||||||
await flowEditorPage.screenshot({
|
await flowEditorPage.screenshot({
|
||||||
path: 'Scheduler trigger test output.png',
|
path: 'Scheduler trigger test output.png',
|
||||||
});
|
});
|
||||||
@@ -142,12 +143,12 @@ test(
|
|||||||
|
|
||||||
await test.step('test trigger substep', async () => {
|
await test.step('test trigger substep', async () => {
|
||||||
await test.step('show sample output', async () => {
|
await test.step('show sample output', async () => {
|
||||||
await expect(flowEditorPage.testOutput).not.toBeVisible();
|
await expect(flowEditorPage.testOuput).not.toBeVisible();
|
||||||
await page
|
await page
|
||||||
.getByTestId('flow-substep-continue-button')
|
.getByTestId('flow-substep-continue-button')
|
||||||
.first()
|
.first()
|
||||||
.click();
|
.click();
|
||||||
await expect(flowEditorPage.testOutput).toBeVisible();
|
await expect(flowEditorPage.testOuput).toBeVisible();
|
||||||
await flowEditorPage.screenshot({
|
await flowEditorPage.screenshot({
|
||||||
path: 'Ntfy action test output.png',
|
path: 'Ntfy action test output.png',
|
||||||
});
|
});
|
||||||
|
@@ -7,7 +7,6 @@ import DialogContentText from '@mui/material/DialogContentText';
|
|||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { AppPropType } from 'propTypes/propTypes';
|
import { AppPropType } from 'propTypes/propTypes';
|
||||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
||||||
@@ -18,6 +17,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
|||||||
import { generateExternalLink } from 'helpers/translationValues';
|
import { generateExternalLink } from 'helpers/translationValues';
|
||||||
import { Form } from './style';
|
import { Form } from './style';
|
||||||
import useAppAuth from 'hooks/useAppAuth';
|
import useAppAuth from 'hooks/useAppAuth';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
function AddAppConnection(props) {
|
function AddAppConnection(props) {
|
||||||
const { application, connectionId, onClose } = props;
|
const { application, connectionId, onClose } = props;
|
||||||
|
@@ -41,7 +41,6 @@ function EditableTypography(props) {
|
|||||||
if (editing) {
|
if (editing) {
|
||||||
component = (
|
component = (
|
||||||
<TextField
|
<TextField
|
||||||
data-test="editableTypographyInput"
|
|
||||||
onClick={handleTextFieldClick}
|
onClick={handleTextFieldClick}
|
||||||
onKeyDown={handleTextFieldKeyDown}
|
onKeyDown={handleTextFieldKeyDown}
|
||||||
onBlur={handleTextFieldBlur}
|
onBlur={handleTextFieldBlur}
|
||||||
|
@@ -101,7 +101,6 @@ export default function EditorLayout() {
|
|||||||
|
|
||||||
{!isFlowLoading && (
|
{!isFlowLoading && (
|
||||||
<EditableTypography
|
<EditableTypography
|
||||||
data-test="editableTypography"
|
|
||||||
variant="body1"
|
variant="body1"
|
||||||
onConfirm={onFlowNameUpdate}
|
onConfirm={onFlowNameUpdate}
|
||||||
noWrap
|
noWrap
|
||||||
|
@@ -12,7 +12,6 @@ import TextField from 'components/TextField';
|
|||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useCreateAccessToken from 'hooks/useCreateAccessToken';
|
import useCreateAccessToken from 'hooks/useCreateAccessToken';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import usePreventUsersFromUpdatingTheirProfile from 'hooks/usePreventUsersFromUpdatingTheirProfile';
|
|
||||||
|
|
||||||
function LoginForm() {
|
function LoginForm() {
|
||||||
const isCloud = useCloud();
|
const isCloud = useCloud();
|
||||||
@@ -20,7 +19,6 @@ function LoginForm() {
|
|||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const authentication = useAuthentication();
|
const authentication = useAuthentication();
|
||||||
const preventUsersFromUpdatingTheirProfile = usePreventUsersFromUpdatingTheirProfile();
|
|
||||||
const { mutateAsync: createAccessToken, isPending: loading } =
|
const { mutateAsync: createAccessToken, isPending: loading } =
|
||||||
useCreateAccessToken();
|
useCreateAccessToken();
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ function LoginForm() {
|
|||||||
sx={{ mb: 1 }}
|
sx={{ mb: 1 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isCloud && !preventUsersFromUpdatingTheirProfile && (
|
{isCloud && (
|
||||||
<Link
|
<Link
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
to={URLS.FORGOT_PASSWORD}
|
to={URLS.FORGOT_PASSWORD}
|
||||||
|
@@ -25,40 +25,25 @@ const process = ({ data, parentKey, index, parentLabel = '' }) => {
|
|||||||
sampleValue: JSON.stringify(sampleValue),
|
sampleValue: JSON.stringify(sampleValue),
|
||||||
};
|
};
|
||||||
|
|
||||||
const arrayItems = sampleValue.flatMap((item, index) => {
|
const arrayItems = sampleValue.flatMap((item, index) =>
|
||||||
const itemItself = {
|
process({
|
||||||
label: `${label}.${index}`,
|
|
||||||
value: `${value}.${index}`,
|
|
||||||
sampleValue: JSON.stringify(item),
|
|
||||||
};
|
|
||||||
|
|
||||||
const itemEntries = process({
|
|
||||||
data: item,
|
data: item,
|
||||||
parentKey: value,
|
parentKey: value,
|
||||||
index,
|
index,
|
||||||
parentLabel: label,
|
parentLabel: label,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return [itemItself].concat(itemEntries);
|
// TODO: remove spreading
|
||||||
});
|
return [arrayItself, ...arrayItems];
|
||||||
|
|
||||||
return [arrayItself].concat(arrayItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof sampleValue === 'object' && sampleValue !== null) {
|
if (typeof sampleValue === 'object' && sampleValue !== null) {
|
||||||
const objectItself = {
|
return process({
|
||||||
label,
|
|
||||||
value,
|
|
||||||
sampleValue: JSON.stringify(sampleValue),
|
|
||||||
};
|
|
||||||
|
|
||||||
const objectEntries = process({
|
|
||||||
data: sampleValue,
|
data: sampleValue,
|
||||||
parentKey: value,
|
parentKey: value,
|
||||||
parentLabel: label,
|
parentLabel: label,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [objectItself].concat(objectEntries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@@ -40,10 +40,6 @@ function Switch(props) {
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
className={className}
|
className={className}
|
||||||
{...FormControlLabelProps}
|
{...FormControlLabelProps}
|
||||||
componentsProps={{
|
|
||||||
typography: {
|
|
||||||
display: 'flex',
|
|
||||||
}}}
|
|
||||||
control={
|
control={
|
||||||
<MuiSwitch
|
<MuiSwitch
|
||||||
{...switchProps}
|
{...switchProps}
|
||||||
|
@@ -118,7 +118,7 @@ function TestSubstep(props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{hasNoOutput && (
|
{hasNoOutput && (
|
||||||
<Alert data-test="flow-test-substep-no-output" severity="warning" sx={{ mb: 1, width: '100%' }}>
|
<Alert severity="warning" sx={{ mb: 1, width: '100%' }}>
|
||||||
<AlertTitle sx={{ fontWeight: 700 }}>
|
<AlertTitle sx={{ fontWeight: 700 }}>
|
||||||
{formatMessage('flowEditor.noTestDataTitle')}
|
{formatMessage('flowEditor.noTestDataTitle')}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
|
||||||
|
|
||||||
export default function usePreventUsersFromUpdatingTheirProfile() {
|
|
||||||
const { data, isSuccess } =
|
|
||||||
useAutomatischConfig();
|
|
||||||
const automatischConfig = data?.data;
|
|
||||||
|
|
||||||
const preventUsersFromUpdatingTheirProfile = isSuccess ? automatischConfig['userManagement.preventUsersFromUpdatingTheirProfile'] : false;
|
|
||||||
|
|
||||||
console.log('preventUsersFromUpdatingTheirProfile', preventUsersFromUpdatingTheirProfile, automatischConfig)
|
|
||||||
|
|
||||||
return preventUsersFromUpdatingTheirProfile;
|
|
||||||
}
|
|
@@ -259,26 +259,20 @@
|
|||||||
"userInterfacePage.primaryLightColorFieldLabel": "Primary light color",
|
"userInterfacePage.primaryLightColorFieldLabel": "Primary light color",
|
||||||
"userInterfacePage.svgDataFieldLabel": "Logo SVG code",
|
"userInterfacePage.svgDataFieldLabel": "Logo SVG code",
|
||||||
"userInterfacePage.submit": "Update",
|
"userInterfacePage.submit": "Update",
|
||||||
"authenticationPage.title": "Authentication",
|
"authenticationPage.title": "Single Sign-On with SAML",
|
||||||
"authenticationConfig.title": "User management",
|
"authenticationForm.active": "Active",
|
||||||
"authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfile": "Prevent users from updating their profile",
|
"authenticationForm.name": "Name",
|
||||||
"authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfileTooltip": "This will prevent users from updating their full name, email and password. This is useful when you want to manage users from your own identity provider.",
|
"authenticationForm.certificate": "Certificate",
|
||||||
"authenticationConfig.save": "Save",
|
"authenticationForm.signatureAlgorithm": "Signature algorithm",
|
||||||
"authenticationConfig.successfullySaved": "The configuration has been saved.",
|
"authenticationForm.issuer": "Issuer",
|
||||||
"samlAuthenticationPage.title": "Single Sign-On with SAML",
|
"authenticationForm.entryPoint": "Entry point",
|
||||||
"samlAuthenticationForm.active": "Active",
|
"authenticationForm.firstnameAttributeName": "Firstname attribute name",
|
||||||
"samlAuthenticationForm.name": "Name",
|
"authenticationForm.surnameAttributeName": "Surname attribute name",
|
||||||
"samlAuthenticationForm.certificate": "Certificate",
|
"authenticationForm.emailAttributeName": "Email attribute name",
|
||||||
"samlAuthenticationForm.signatureAlgorithm": "Signature algorithm",
|
"authenticationForm.roleAttributeName": "Role attribute name",
|
||||||
"samlAuthenticationForm.issuer": "Issuer",
|
"authenticationForm.defaultRole": "Default role",
|
||||||
"samlAuthenticationForm.entryPoint": "Entry point",
|
"authenticationForm.successfullySaved": "The provider has been saved.",
|
||||||
"samlAuthenticationForm.firstnameAttributeName": "Firstname attribute name",
|
"authenticationForm.save": "Save",
|
||||||
"samlAuthenticationForm.surnameAttributeName": "Surname attribute name",
|
|
||||||
"samlAuthenticationForm.emailAttributeName": "Email attribute name",
|
|
||||||
"samlAuthenticationForm.roleAttributeName": "Role attribute name",
|
|
||||||
"samlAuthenticationForm.defaultRole": "Default role",
|
|
||||||
"samlAuthenticationForm.successfullySaved": "The provider has been saved.",
|
|
||||||
"samlAuthenticationForm.save": "Save",
|
|
||||||
"roleMappingsForm.title": "Role mappings",
|
"roleMappingsForm.title": "Role mappings",
|
||||||
"roleMappingsForm.remoteRoleName": "Remote role name",
|
"roleMappingsForm.remoteRoleName": "Remote role name",
|
||||||
"roleMappingsForm.role": "Role",
|
"roleMappingsForm.role": "Role",
|
||||||
|
@@ -1,98 +0,0 @@
|
|||||||
import { useMutation } from '@apollo/client';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Switch from 'components/Switch';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
|
||||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
|
||||||
|
|
||||||
import Form from 'components/Form';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
|
||||||
import { UPDATE_CONFIG } from 'graphql/mutations/update-config.ee';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
function AuthenticationConfig() {
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { data, isLoading: isAutomatischConfigLoading } =
|
|
||||||
useAutomatischConfig();
|
|
||||||
const automatischConfig = data?.data;
|
|
||||||
|
|
||||||
const [
|
|
||||||
updateConfig,
|
|
||||||
{ loading: updateConfigLoading },
|
|
||||||
] = useMutation(UPDATE_CONFIG);
|
|
||||||
|
|
||||||
const handleSubmit = async (values) => {
|
|
||||||
try {
|
|
||||||
await updateConfig({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
'userManagement.preventUsersFromUpdatingTheirProfile': values.userManagement.preventUsersFromUpdatingTheirProfile,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ['automatisch', 'config'],
|
|
||||||
});
|
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('authenticationConfig.successfullySaved'), {
|
|
||||||
variant: 'success',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-update-role-mappings-success',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed while saving!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isAutomatischConfigLoading) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography variant="h4">
|
|
||||||
{formatMessage('authenticationConfig.title')}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Form defaultValues={automatischConfig} onSubmit={handleSubmit}>
|
|
||||||
<Stack direction="column" spacing={2}>
|
|
||||||
<Switch
|
|
||||||
name="userManagement.preventUsersFromUpdatingTheirProfile"
|
|
||||||
label={<>
|
|
||||||
{formatMessage('authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfile')}
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
title={formatMessage('authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfileTooltip')}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
>
|
|
||||||
<InfoOutlinedIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoadingButton
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
sx={{ boxShadow: 2 }}
|
|
||||||
loading={updateConfigLoading}
|
|
||||||
>
|
|
||||||
{formatMessage('authenticationConfig.save')}
|
|
||||||
</LoadingButton>
|
|
||||||
</Stack>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthenticationConfig.propTypes = {
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthenticationConfig;
|
|
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
@@ -84,6 +85,7 @@ function RoleMappings({ provider, providerLoading }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Divider sx={{ pt: 2 }} />
|
||||||
<Typography variant="h3">
|
<Typography variant="h3">
|
||||||
{formatMessage('roleMappingsForm.title')}
|
{formatMessage('roleMappingsForm.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@@ -75,7 +75,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('samlAuthenticationForm.successfullySaved'), {
|
enqueueSnackbar(formatMessage('authenticationForm.successfullySaved'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
'data-test': 'snackbar-save-saml-provider-success',
|
'data-test': 'snackbar-save-saml-provider-success',
|
||||||
@@ -98,18 +98,18 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
<Switch
|
<Switch
|
||||||
name="active"
|
name="active"
|
||||||
label={formatMessage('samlAuthenticationForm.active')}
|
label={formatMessage('authenticationForm.active')}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="name"
|
name="name"
|
||||||
label={formatMessage('samlAuthenticationForm.name')}
|
label={formatMessage('authenticationForm.name')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="certificate"
|
name="certificate"
|
||||||
label={formatMessage('samlAuthenticationForm.certificate')}
|
label={formatMessage('authenticationForm.certificate')}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
@@ -126,44 +126,44 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('samlAuthenticationForm.signatureAlgorithm')}
|
label={formatMessage('authenticationForm.signatureAlgorithm')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="issuer"
|
name="issuer"
|
||||||
label={formatMessage('samlAuthenticationForm.issuer')}
|
label={formatMessage('authenticationForm.issuer')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="entryPoint"
|
name="entryPoint"
|
||||||
label={formatMessage('samlAuthenticationForm.entryPoint')}
|
label={formatMessage('authenticationForm.entryPoint')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="firstnameAttributeName"
|
name="firstnameAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.firstnameAttributeName')}
|
label={formatMessage('authenticationForm.firstnameAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="surnameAttributeName"
|
name="surnameAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.surnameAttributeName')}
|
label={formatMessage('authenticationForm.surnameAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="emailAttributeName"
|
name="emailAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.emailAttributeName')}
|
label={formatMessage('authenticationForm.emailAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="roleAttributeName"
|
name="roleAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.roleAttributeName')}
|
label={formatMessage('authenticationForm.roleAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
@@ -175,7 +175,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('samlAuthenticationForm.defaultRole')}
|
label={formatMessage('authenticationForm.defaultRole')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
loading={isRolesLoading}
|
loading={isRolesLoading}
|
||||||
@@ -187,7 +187,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
sx={{ boxShadow: 2 }}
|
sx={{ boxShadow: 2 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{formatMessage('samlAuthenticationForm.save')}
|
{formatMessage('authenticationForm.save')}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Form>
|
</Form>
|
||||||
|
@@ -2,14 +2,11 @@ import Grid from '@mui/material/Grid';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
|
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
|
||||||
import AuthenticationConfig from './AuthenticationConfig';
|
|
||||||
import SamlConfiguration from './SamlConfiguration';
|
import SamlConfiguration from './SamlConfiguration';
|
||||||
import RoleMappings from './RoleMappings';
|
import RoleMappings from './RoleMappings';
|
||||||
import useAdminSamlAuthProviders from 'hooks/useAdminSamlAuthProviders.ee';
|
import useAdminSamlAuthProviders from 'hooks/useAdminSamlAuthProviders.ee';
|
||||||
|
|
||||||
function AuthenticationPage() {
|
function AuthenticationPage() {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
@@ -19,37 +16,20 @@ function AuthenticationPage() {
|
|||||||
const { data, isLoading: isProviderLoading } = useSamlAuthProvider({
|
const { data, isLoading: isProviderLoading } = useSamlAuthProvider({
|
||||||
samlAuthProviderId,
|
samlAuthProviderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const provider = data?.data;
|
const provider = data?.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Grid container item xs={12} sm={10} md={9}>
|
<Grid container item xs={12} sm={10} md={9}>
|
||||||
<Grid container item xs={12}>
|
<Grid container item xs={12} sx={{ mb: [2, 5] }}>
|
||||||
<PageTitle>{formatMessage('authenticationPage.title')}</PageTitle>
|
<PageTitle>{formatMessage('authenticationPage.title')}</PageTitle>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
|
||||||
<Stack spacing={5}>
|
|
||||||
<AuthenticationConfig />
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
</Stack>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid container item xs={12}>
|
|
||||||
<PageTitle variant="h4">{formatMessage('samlAuthenticationPage.title')}</PageTitle>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
||||||
<Stack spacing={5}>
|
<Stack spacing={5}>
|
||||||
<SamlConfiguration
|
<SamlConfiguration
|
||||||
provider={provider}
|
provider={provider}
|
||||||
providerLoading={isProviderLoading}
|
providerLoading={isProviderLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<RoleMappings
|
<RoleMappings
|
||||||
provider={provider}
|
provider={provider}
|
||||||
providerLoading={isProviderLoading}
|
providerLoading={isProviderLoading}
|
||||||
|
@@ -10,7 +10,6 @@ export default function Login() {
|
|||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
|
||||||
<SsoProviders />
|
<SsoProviders />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
@@ -29,14 +29,12 @@ import adminSettingsRoutes from './adminSettingsRoutes';
|
|||||||
import Notifications from 'pages/Notifications';
|
import Notifications from 'pages/Notifications';
|
||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
import useAuthentication from 'hooks/useAuthentication';
|
import useAuthentication from 'hooks/useAuthentication';
|
||||||
import usePreventUsersFromUpdatingTheirProfile from 'hooks/usePreventUsersFromUpdatingTheirProfile';
|
|
||||||
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
import Installation from 'pages/Installation';
|
import Installation from 'pages/Installation';
|
||||||
|
|
||||||
function Routes() {
|
function Routes() {
|
||||||
const { data: automatischInfo, isSuccess } = useAutomatischInfo();
|
const { data: automatischInfo, isSuccess } = useAutomatischInfo();
|
||||||
const { data: configData } = useAutomatischConfig();
|
const { data: configData } = useAutomatischConfig();
|
||||||
const preventUsersFromUpdatingTheirProfile = usePreventUsersFromUpdatingTheirProfile();
|
|
||||||
const { isAuthenticated } = useAuthentication();
|
const { isAuthenticated } = useAuthentication();
|
||||||
const config = configData?.data;
|
const config = configData?.data;
|
||||||
|
|
||||||
@@ -136,14 +134,14 @@ function Routes() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{preventUsersFromUpdatingTheirProfile === false && <Route
|
<Route
|
||||||
path={URLS.FORGOT_PASSWORD}
|
path={URLS.FORGOT_PASSWORD}
|
||||||
element={
|
element={
|
||||||
<PublicLayout>
|
<PublicLayout>
|
||||||
<ForgotPassword />
|
<ForgotPassword />
|
||||||
</PublicLayout>
|
</PublicLayout>
|
||||||
}
|
}
|
||||||
/>}
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.RESET_PASSWORD}
|
path={URLS.RESET_PASSWORD}
|
||||||
|
@@ -95,11 +95,11 @@ export const defaultTheme = createTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
h4: {
|
h4: {
|
||||||
fontSize: referenceTheme.typography.pxToRem(28),
|
fontSize: referenceTheme.typography.pxToRem(32),
|
||||||
lineHeight: 1.3,
|
lineHeight: 1.3,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
[referenceTheme.breakpoints.down('sm')]: {
|
[referenceTheme.breakpoints.down('sm')]: {
|
||||||
fontSize: referenceTheme.typography.pxToRem(22),
|
fontSize: referenceTheme.typography.pxToRem(16),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
h5: {
|
h5: {
|
||||||
|
Reference in New Issue
Block a user