From fd51b3e76eda90981c10aacbe70e9c13b722dee0 Mon Sep 17 00:00:00 2001 From: radiantly Date: Fri, 15 Oct 2021 15:37:51 +0530 Subject: [PATCH] ui: Allow ${} interpolation for template URLs --- .../consul-ui/app/helpers/render-template.js | 11 +- .../helpers/render-template-test.js | 128 +++++++++++++++++- website/content/docs/agent/options.mdx | 8 +- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/ui/packages/consul-ui/app/helpers/render-template.js b/ui/packages/consul-ui/app/helpers/render-template.js index 57c006b84c..071f2d1df1 100644 --- a/ui/packages/consul-ui/app/helpers/render-template.js +++ b/ui/packages/consul-ui/app/helpers/render-template.js @@ -1,8 +1,15 @@ import Helper from '@ember/component/helper'; import { inject as service } from '@ember/service'; -// simple mustache regexp `/{{item.Name}}/` -const templateRe = /{{([A-Za-z.0-9_-]+)}}/g; +// regexp that matches {{item.Name}} or ${item.Name} +// what this regex does +// (?:\$|\{) - Match either $ or { +// \{ - Match { +// ([a-z.0-9_-]+) - Capturing group +// (?:(?<=\$\{[^{]+) - Use a positive lookbehind to assert that ${ was matched previously +// |\} ) - or match a } +// \} - Match } +const templateRe = /(?:\$|\{)\{([a-z.0-9_-]+)(?:(?<=\$\{[^{]+)|\})\}/gi; let render; export default class RenderTemplateHelper extends Helper { @service('encoder') encoder; diff --git a/ui/packages/consul-ui/tests/integration/helpers/render-template-test.js b/ui/packages/consul-ui/tests/integration/helpers/render-template-test.js index 6136ca7ac3..0eb7736300 100644 --- a/ui/packages/consul-ui/tests/integration/helpers/render-template-test.js +++ b/ui/packages/consul-ui/tests/integration/helpers/render-template-test.js @@ -94,7 +94,133 @@ module('Integration | Helper | render-template', function(hooks) { result: 'http://localhost/?=%23Na%2Fme', }, ].forEach(item => { - test('it renders', async function(assert) { + test('it renders {{}} style interpolation`', async function(assert) { + this.set('template', item.href); + this.set('vars', item.vars); + + await render(hbs`{{render-template template vars}}`); + + assert.equal(this.element.textContent.trim(), item.result); + }); + }); + + [ + { + href: 'http://localhost/?=${Name}/${ID}', + vars: { + Name: 'name', + ID: 'id', + }, + result: 'http://localhost/?=name/id', + }, + { + href: 'http://localhost/?=${Name}/${ID}', + vars: { + Name: '{{Name}}', + ID: '{{ID}}', + }, + result: 'http://localhost/?=%7B%7BName%7D%7D/%7B%7BID%7D%7D', + }, + { + href: 'http://localhost/?=${deep.Name}/${deep.ID}', + vars: { + deep: { + Name: '{{Name}}', + ID: '{{ID}}', + }, + }, + result: 'http://localhost/?=%7B%7BName%7D%7D/%7B%7BID%7D%7D', + }, + { + href: 'http://localhost/?=${}/${}', + vars: { + Name: 'name', + ID: 'id', + }, + // If you don't pass actual variables then nothing + // gets replaced and nothing is URL encoded + result: 'http://localhost/?=${}/${}', + }, + { + href: 'http://localhost/?=${Service_Name}/${Meta-Key}', + vars: { + Service_Name: 'name', + ['Meta-Key']: 'id', + }, + result: 'http://localhost/?=name/id', + }, + { + href: 'http://localhost/?=${Service_Name}/${Meta-Key}', + vars: { + WrongPropertyName: 'name', + ['Meta-Key']: 'id', + }, + result: 'http://localhost/?=/id', + }, + { + href: 'http://localhost/?=${.Name}', + vars: { + ['.Name']: 'name', + }, + result: 'http://localhost/?=', + }, + { + href: 'http://localhost/?=${.}', + vars: { + ['.']: 'name', + }, + result: 'http://localhost/?=', + }, + { + href: 'http://localhost/?=${deep..Name}', + vars: { + deep: { + Name: 'Name', + ID: 'ID', + }, + }, + result: 'http://localhost/?=', + }, + { + href: 'http://localhost/?=${deep.Name}', + vars: { + deep: { + Name: '#Na/me', + ID: 'ID', + }, + }, + result: 'http://localhost/?=%23Na%2Fme', + }, + ].forEach(item => { + test('it renders ${} style interpolation', async function(assert) { + this.set('template', item.href); + this.set('vars', item.vars); + + await render(hbs`{{render-template template vars}}`); + + assert.equal(this.element.textContent.trim(), item.result); + }); + }); + + [ + { + href: 'http://localhost/?=${Name}/{{ID}}', + vars: { + Name: 'name', + ID: 'id', + }, + result: 'http://localhost/?=name/id', + }, + { + href: 'http://localhost/?=${Name}}/{{ID}', + vars: { + Name: 'name', + ID: 'id', + }, + result: 'http://localhost/?=name}/{{ID}', + }, + ].forEach(item => { + test('it renders both styles of interpolation when used together', async function(assert) { this.set('template', item.href); this.set('vars', item.vars); diff --git a/website/content/docs/agent/options.mdx b/website/content/docs/agent/options.mdx index 2b2c276e84..93dea72b1c 100644 --- a/website/content/docs/agent/options.mdx +++ b/website/content/docs/agent/options.mdx @@ -2174,9 +2174,11 @@ bind_addr = "{{ GetPrivateInterfaces | include \"network\" \"10.0.0.0/8\" | attr The placeholders available are: - - `{{Service.Name}}` - Replaced with the current service's name. - - `{{Service.Namespace}}` - Replaced with the current service's namespace or empty if namespaces are not enabled. - - `{{Datacenter}}` - Replaced with the current service's datacenter. + - `{{Service.Name}}` or `${Service.Name}` - Replaced with the current service's name. + - `{{Service.Namespace}}` or `${Service.Namespace}` - Replaced with the current service's namespace or empty if namespaces are not enabled. + - `{{Datacenter}}` or `${Datacenter}` - Replaced with the current service's datacenter. + + ~> **Note:** The `${}` style interpolation is only available from Consul TBD. - `ui_dir` - **This field is deprecated in Consul 1.9.0. See the [`ui_config.dir`](#ui_config_dir) field instead.** Equivalent to the [`-ui-dir`](#_ui_dir) command-line