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

Add the ability to inject variables in the "Http Request" action in scenes #1540

Merged
merged 2 commits into from
May 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
131 changes: 131 additions & 0 deletions front/src/components/scene/TextWithVariablesInjected.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Component } from 'preact';
import Tagify from '@yaireo/tagify';
import '@yaireo/tagify/dist/tagify.css';
import get from 'get-value';

const OPENING_VARIABLE = '{{';
const CLOSING_VARIABLE = '}}';

class TextWithVariablesInjected extends Component {
setRef = dom => (this.tagifyInputRef = dom);
initTagify = () => {
if (this.tagify) {
this.tagify.destroy();
}
this.tagify = new Tagify(this.tagifyInputRef, {
mode: 'mix',
pattern: new RegExp(OPENING_VARIABLE),
duplicates: true,
enforceWhitelist: true,
tagTextProp: 'text',
dropdown: {
enabled: 1,
position: 'text',
mapValueTo: 'title',
maxItems: 200
},
whitelist: this.state.variableWhileList,
mixTagsInterpolator: [OPENING_VARIABLE, CLOSING_VARIABLE]
});
const text = this.props.text || '';
this.tagify.loadOriginalValues(text);
this.tagify.on('input add remove change', () => {
const text = get(this.tagify, 'DOM.input.innerText', '');
this.parseText(text);
});
};
refreshVariables = async nextProps => {
const variableWhileList = [];
let variablesKey = '';
let variableReady = null;
// Action variables
nextProps.actionsGroupsBefore.forEach((actionGroup, groupIndex) => {
actionGroup.forEach((action, index) => {
if (nextProps.variables[groupIndex][index]) {
nextProps.variables[groupIndex][index].forEach(option => {
if (option.ready && variableReady === null) {
variableReady = true;
}
if (!option.ready) {
variableReady = false;
}
// we create a "variablesKey" string to quickly compare the variables displayed
// instead of having to loop through 2 arrays. It's quicker :)
variablesKey += `${groupIndex}.${index}.${option.name}.${option.label}.${option.ready}`;
variableWhileList.push({
id: `${groupIndex}.${index}.${option.name}`,
text: `${groupIndex + 1}. ${index + 1}. ${option.label}`,
title: `${groupIndex + 1}. ${index + 1}. ${option.label}`,
value: `${groupIndex}.${index}.${option.name}`
});
});
}
});
});
// Triggers variables
nextProps.triggersVariables.forEach((triggerVariables, index) => {
triggerVariables.forEach(triggerVariable => {
if (triggerVariable.ready && variableReady === null) {
variableReady = true;
}
if (!triggerVariable.ready) {
variableReady = false;
}
// we create a "variablesKey" string to quickly compare the variables displayed
// instead of having to loop through 2 arrays. It's quicker :)
variablesKey += `trigger.${index}.${triggerVariable.name}.${triggerVariable.label}.${triggerVariable.ready}`;
variableWhileList.push({
id: `triggerEvent.${triggerVariable.name}`,
text: `${index + 1}. ${triggerVariable.label}`,
title: `${index + 1}. ${triggerVariable.label}`,
value: `triggerEvent.${triggerVariable.name}`
});
});
});
const previousVariablesKey = this.state.variablesKey;
await this.setState({ variableWhileList, variableReady, variablesKey });
// we compare here the previous variables key
// and the new one, and we compare if they are the same
if (variablesKey !== previousVariablesKey) {
this.initTagify();
}
};
parseText = textContent => {
let text = textContent ? textContent : '';
this.state.variableWhileList.forEach(variable => {
text = text.replaceAll(variable.text, `${OPENING_VARIABLE}${variable.id}${CLOSING_VARIABLE}`);
});
text = text.replaceAll(`\n${OPENING_VARIABLE}`, OPENING_VARIABLE);
text = text.replaceAll(`${CLOSING_VARIABLE}\n`, CLOSING_VARIABLE);
text = text.trim();
this.props.updateText(text);
};
constructor(props) {
super(props);
this.props = props;
this.state = {
variableWhileList: []
};
}

componentDidMount() {
this.initTagify();
}
componentWillReceiveProps(nextProps) {
this.refreshVariables(nextProps);
}
componentWillUnmount() {
if (this.tagify) {
this.tagify.destroy();
}
}

render({ singleLineInput = false, placeholder }, {}) {
if (singleLineInput) {
return <input type="text" ref={this.setRef} placeholder={placeholder} class="form-control" />;
}
return <textarea ref={this.setRef} placeholder={placeholder} class="form-control" />;
}
}

export default TextWithVariablesInjected;
1 change: 1 addition & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,7 @@
"headersValuePlaceholder": "Value",
"responseData": "Response",
"tryButton": "Try request",
"variablesExplanation": "To inject a variable defined in a \"Get device value\" action, type \"{{\". This variable will be injected only during scene execution, not when clicking on \"Try request\" below.",
"requestError": "An error happened while making the request. Are you sure parameters are right?"
},
"scene": {
Expand Down
1 change: 1 addition & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,7 @@
"headersValuePlaceholder": "Valeur",
"responseData": "Réponse",
"tryButton": "Essayer",
"variablesExplanation": "Pour injecter une variable défini dans un bloc \"Récupérer le dernier état\", tapez \"{{\". Cette variable ne sera injecté que lors de l'exécution de la scène, et non pas lors du clic sur le bouton \"Essayer\".",
"requestError": "Une erreur est survenue lors de la requête. Êtes vous sûr d'avoir bien rempli le formulaire ?"
},
"scene": {
Expand Down
3 changes: 3 additions & 0 deletions front/src/routes/scene/edit-scene/ActionCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ const ActionCard = ({ children, ...props }) => (
index={props.index}
updateActionProperty={props.updateActionProperty}
setVariables={props.setVariables}
actionsGroupsBefore={props.actionsGroupsBefore}
variables={props.variables}
triggersVariables={props.triggersVariables}
/>
)}
{props.action.type === ACTIONS.CONDITION.CHECK_TIME && (
Expand Down
28 changes: 19 additions & 9 deletions front/src/routes/scene/edit-scene/actions/HttpRequest.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import { Text, Localizer } from 'preact-i18n';
import cx from 'classnames';
import update from 'immutability-helper';

import TextWithVariablesInjected from '../../../../components/scene/TextWithVariablesInjected';

const METHOD_WITH_BODY = ['post', 'patch', 'put'];

const helpTextStyle = {
fontSize: 12,
marginBottom: '.375rem'
};

const getAllPropertiesObject = (obj, path = '', results = []) => {
Object.keys(obj).forEach(key => {
const value = obj[key];
Expand Down Expand Up @@ -70,7 +77,6 @@ class Header extends Component {
}
}

@connect('httpClient', {})
class HttpRequestAction extends Component {
handleChangeMethod = e => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'method', e.target.value);
Expand All @@ -81,8 +87,8 @@ class HttpRequestAction extends Component {
handleChangeUrl = e => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'url', e.target.value);
};
handleChangeBody = e => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'body', e.target.value);
handleChangeBody = text => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'body', text);
};
addNewHeader = e => {
e.preventDefault();
Expand Down Expand Up @@ -260,12 +266,16 @@ class HttpRequestAction extends Component {
<Text id="global.requiredField" />
</span>
</label>
<div style={helpTextStyle}>
<Text id="editScene.actionsCard.httpRequest.variablesExplanation" />
</div>
<Localizer>
<textarea
type="text"
class="form-control"
value={props.action.body}
onChange={this.handleChangeBody}
<TextWithVariablesInjected
text={props.action.body}
updateText={this.handleChangeBody}
triggersVariables={props.triggersVariables}
actionsGroupsBefore={props.actionsGroupsBefore}
variables={props.variables}
placeholder={<Text id="editScene.actionsCard.httpRequest.bodyPlaceholder" />}
/>
</Localizer>
Expand Down Expand Up @@ -297,4 +307,4 @@ class HttpRequestAction extends Component {
}
}

export default HttpRequestAction;
export default connect('httpClient', {})(HttpRequestAction);
123 changes: 12 additions & 111 deletions front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import Select from 'react-select';
import Tagify from '@yaireo/tagify';
import '@yaireo/tagify/dist/tagify.css';
import { Component } from 'preact';
import get from 'get-value';
import { connect } from 'unistore/preact';
import { Text } from 'preact-i18n';

import TextWithVariablesInjected from '../../../../components/scene/TextWithVariablesInjected';

const helpTextStyle = {
fontSize: 12,
marginBottom: '.375rem'
};

const OPENING_VARIABLE = '{{';
const CLOSING_VARIABLE = '}}';

class SendMessageParams extends Component {
getOptions = async () => {
try {
Expand All @@ -32,8 +28,8 @@ class SendMessageParams extends Component {
console.error(e);
}
};
handleChangeText = e => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'text', e.target.value);
updateText = text => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'text', text);
};
handleChange = selectedOption => {
if (selectedOption && selectedOption.value) {
Expand All @@ -53,119 +49,18 @@ class SendMessageParams extends Component {
}
this.setState({ selectedOption });
};
refreshVariables = async nextProps => {
const variableWhileList = [];
let variablesKey = '';
let variableReady = null;
// Action variables
nextProps.actionsGroupsBefore.forEach((actionGroup, groupIndex) => {
actionGroup.forEach((action, index) => {
if (nextProps.variables[groupIndex][index]) {
nextProps.variables[groupIndex][index].forEach(option => {
if (option.ready && variableReady === null) {
variableReady = true;
}
if (!option.ready) {
variableReady = false;
}
// we create a "variablesKey" string to quickly compare the variables displayed
// instead of having to loop through 2 arrays. It's quicker :)
variablesKey += `${groupIndex}.${index}.${option.name}.${option.label}.${option.ready}`;
variableWhileList.push({
id: `${groupIndex}.${index}.${option.name}`,
text: `${groupIndex + 1}. ${index + 1}. ${option.label}`,
title: `${groupIndex + 1}. ${index + 1}. ${option.label}`,
value: `${groupIndex}.${index}.${option.name}`
});
});
}
});
});
// Triggers variables
nextProps.triggersVariables.forEach((triggerVariables, index) => {
triggerVariables.forEach(triggerVariable => {
if (triggerVariable.ready && variableReady === null) {
variableReady = true;
}
if (!triggerVariable.ready) {
variableReady = false;
}
// we create a "variablesKey" string to quickly compare the variables displayed
// instead of having to loop through 2 arrays. It's quicker :)
variablesKey += `trigger.${index}.${triggerVariable.name}.${triggerVariable.label}.${triggerVariable.ready}`;
variableWhileList.push({
id: `triggerEvent.${triggerVariable.name}`,
text: `${index + 1}. ${triggerVariable.label}`,
title: `${index + 1}. ${triggerVariable.label}`,
value: `triggerEvent.${triggerVariable.name}`
});
});
});
const previousVariablesKey = this.state.variablesKey;
await this.setState({ variableWhileList, variableReady, variablesKey });
// we compare here the previous variables key
// and the new one, and we compare if they are the same
if (variablesKey !== previousVariablesKey) {
this.initTagify();
}
};
setRef = dom => (this.tagifyInputRef = dom);
initTagify = () => {
if (this.tagify) {
this.tagify.destroy();
}
this.tagify = new Tagify(this.tagifyInputRef, {
mode: 'mix',
pattern: new RegExp(OPENING_VARIABLE),
duplicates: true,
enforceWhitelist: true,
tagTextProp: 'text',
dropdown: {
enabled: 1,
position: 'text',
mapValueTo: 'title',
maxItems: 200
},
whitelist: this.state.variableWhileList,
mixTagsInterpolator: [OPENING_VARIABLE, CLOSING_VARIABLE]
});
const text = this.props.action.text || '';
this.tagify.loadOriginalValues(text);
this.tagify.on('input add remove change', () => {
const text = get(this.tagify, 'DOM.input.innerText', '');
this.parseText(text);
});
};
parseText = textContent => {
let text = textContent ? textContent : '';
this.state.variableWhileList.forEach(variable => {
text = text.replaceAll(variable.text, `${OPENING_VARIABLE}${variable.id}${CLOSING_VARIABLE}`);
});
text = text.replaceAll(`\n${OPENING_VARIABLE}`, OPENING_VARIABLE);
text = text.replaceAll(`${CLOSING_VARIABLE}\n`, CLOSING_VARIABLE);
text = text.trim();
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'text', text);
};
constructor(props) {
super(props);
this.props = props;
this.state = {
selectedOption: '',
variableWhileList: []
selectedOption: ''
};
}
componentDidMount() {
this.getOptions();
this.initTagify();
}
componentWillReceiveProps(nextProps) {
this.refreshSelectedOptions(nextProps);
this.refreshVariables(nextProps);
}
componentWillUnmount() {
if (this.tagify) {
this.tagify.destroy();
}
}
render(props, { selectedOption, userOptions }) {
return (
Expand Down Expand Up @@ -198,7 +93,13 @@ class SendMessageParams extends Component {
<Text id="editScene.actionsCard.messageSend.explanationText" />
</div>
<div className="tags-input">
<textarea ref={this.setRef} class="form-control" />
<TextWithVariablesInjected
text={props.action.text}
triggersVariables={props.triggersVariables}
actionsGroupsBefore={props.actionsGroupsBefore}
variables={props.variables}
updateText={this.updateText}
/>
</div>
</div>
</div>
Expand Down