Skip to content

Commit

Permalink
Merge pull request rancher#10142 from rak-phillip/bugfix/10044-config…
Browse files Browse the repository at this point in the history
…-maps

Allow for `jsyaml.dump()` Options in `dumpBlock`
  • Loading branch information
rak-phillip authored Dec 15, 2023
2 parents 541684f + 2b482f8 commit 0292e11
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 9 deletions.
52 changes: 52 additions & 0 deletions cypress/e2e/po/components/code-mirror.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import { CypressChainable } from '@/cypress/e2e/po/po.types';

export default class CodeMirrorPo extends ComponentPo {
static byLabel(self: CypressChainable, label: string): CodeMirrorPo {
throw new Error('Not implemented');
}

static bySelector(self: CypressChainable, selector: string): CodeMirrorPo {
return new CodeMirrorPo(
self
.find(`${ selector } .CodeMirror`, { includeShadowDom: true })
);
}

/**
* Type value in the input
* @param value Value to be typed
* @returns
*/
set(value: string): Cypress.Chainable {
this.input().should('be.visible');

return this.input()
.then(($codeMirror) => {
const codeMirrorInstance = $codeMirror[0].CodeMirror;

codeMirrorInstance.setValue(value);
});
}

clear() {
return this.input().clear();
}

value(): Cypress.Chainable {
return this.input()
.then(($codeMirror) => {
const codeMirrorInstance = $codeMirror[0].CodeMirror;

return codeMirrorInstance.getValue();
});
}

/**
* Return the input HTML element from given container
* @returns HTML Element
*/
private input(): Cypress.Chainable {
return this.self();
}
}
53 changes: 53 additions & 0 deletions cypress/e2e/po/components/input.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import { CypressChainable } from '@/cypress/e2e/po/po.types';

export default class InputPo extends ComponentPo {
static byLabel(self: CypressChainable, label: string): InputPo {
return new InputPo(
self
.find('.labeled-input', { includeShadowDom: true })
.contains(label)
.next()
);
}

static bySelector(self: CypressChainable, selector: string): InputPo {
return new InputPo(
self
.find(`input${ selector }`, { includeShadowDom: true }).eq(0)
);
}

/**
* Type value in the input
* @param value Value to be typed
* @returns
*/
set(value: string): Cypress.Chainable {
this.input().should('be.visible');
this.input().focus();
this.input().clear();

return this.input().type(value);
}

clear() {
return this.input().clear();
}

value(): Cypress.Chainable {
throw new Error('Not implements');
// The text for the input field is in a shadow dom element. Neither the proposed two methods
// to dive in to the shadow dom work
// return this.input().find('div', { includeShadowDom: true }).invoke('text');
// return this.input().shadow().find('div').invoke('text');
}

/**
* Return the input HTML element from given container
* @returns HTML Element
*/
private input(): Cypress.Chainable {
return this.self();
}
}
31 changes: 31 additions & 0 deletions cypress/e2e/po/components/storage/config-map.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import CreateEditViewPo from '@/cypress/e2e/po/components/create-edit-view.po';
import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po';
import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po';
import InputPo from '@/cypress/e2e/po/components/input.po';
import CodeMirrorPo from '@/cypress/e2e/po/components/code-mirror.po';

export default class ConfigMapPo extends CreateEditViewPo {
constructor(selector = '.dashboard-root') {
super(selector);
}

nameInput() {
return LabeledInputPo.bySelector(this.self(), '[data-testid="name-ns-description-name"]');
}

keyInput() {
return InputPo.bySelector(this.self(), '[data-testid="input-kv-item-key-0"]');
}

valueInput() {
return CodeMirrorPo.bySelector(this.self(), '[data-testid="code-mirror-multiline-field"]');
}

descriptionInput() {
return LabeledInputPo.byLabel(this.self(), 'Description');
}

saveCreateForm(): AsyncButtonPo {
return new AsyncButtonPo('[data-testid="form-save"]', this.self());
}
}
28 changes: 28 additions & 0 deletions cypress/e2e/po/pages/explorer/config-map.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import PagePo from '@/cypress/e2e/po/pages/page.po';
import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po';

export class ConfigMapPagePo extends PagePo {
private static createPath(clusterId: string) {
return `/c/${ clusterId }/explorer/configmap`;
}

static goTo(clusterId: string): Cypress.Chainable<Cypress.AUTWindow> {
return super.goTo(ConfigMapPagePo.createPath(clusterId));
}

constructor(clusterId = 'local') {
super(ConfigMapPagePo.createPath(clusterId));
}

clickCreate() {
const baseResourceList = new BaseResourceList(this.self());

return baseResourceList.masthead().actions().eq(0).click();
}

listElementWithName(name:string) {
const baseResourceList = new BaseResourceList(this.self());

return baseResourceList.resourceTable().sortableTable().rowElementWithName(name);
}
}
56 changes: 56 additions & 0 deletions cypress/e2e/tests/pages/explorer/storage/configmap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ConfigMapPagePo } from '@/cypress/e2e/po/pages/explorer/config-map.po';
import ConfigMapPo from '@/cypress/e2e/po/components/storage/config-map.po';

describe('ConfigMap', () => {
beforeEach(() => {
cy.login();
});

it('creates a configmap and displays it in the list', () => {
const expectedValue = `# Sample XPlanManagerAPI Configuration (if this comment is longer than 80 characters, the output should remain the same)
apiUrl=https://example.com/xplan-api-manager
contactEmailAddress=contact@example.com
termsOfServiceUrl=https://example.com/terms
documentationUrl=https://example.com/docs
wmsUrl=https://example.com/xplan-wms/services
skipSemantic=false
skipGeometric=true`;

// Visit the main menu and select the 'local' cluster
// Navigate to Service Discovery => ConfigMaps
const configMapPage = new ConfigMapPagePo('local');

configMapPage.goTo();

// Click on Create
configMapPage.clickCreate();

// Enter ConfigMap name
// Enter ConfigMap key
// Enter ConfigMap value
// Enter ConfigMap description
const configMapPo = new ConfigMapPo();

configMapPo.nameInput().set('custom-config-map');
configMapPo.keyInput().set('managerApiConfiguration.properties');
configMapPo.valueInput().set(expectedValue);
configMapPo.descriptionInput().set('Custom Config Map Description');

// Click on Create
configMapPo.saveCreateForm().click();

// Check if the ConfigMap is created successfully
configMapPage.listElementWithName('custom-config-map').should('exist');

// Navigate back to the edit page
configMapPage.listElementWithName('custom-config-map')
.find(`button[data-testid="sortable-table-0-action-button"]`)
.click()
.get(`li[data-testid="action-menu-0-item"]`)
.click();

// Assert the current value yaml dumps will append a newline at the end
configMapPo.valueInput().value().should('eq', `${ expectedValue }\n`);
});
});
3 changes: 3 additions & 0 deletions shell/components/form/KeyValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ export default {
:clearable="false"
:taggable="keyTaggable"
:options="calculateOptions(row[keyName])"
:data-testid="`select-kv-item-key-${i}`"
@input="queueUpdate"
/>
<input
Expand All @@ -652,6 +653,7 @@ export default {
v-model="row[keyName]"
:disabled="isView || disabled || !keyEditable || isProtected(row.key)"
:placeholder="keyPlaceholder"
:data-testid="`input-kv-item-key-${i}`"
@input="queueUpdate"
@paste="onPaste(i, $event)"
>
Expand All @@ -661,6 +663,7 @@ export default {
<!-- Value -->
<div
:key="i+'value'"
:data-testid="`kv-item-value-${i}`"
class="kv-item value"
>
<slot
Expand Down
3 changes: 2 additions & 1 deletion shell/edit/configmap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export default {
return {
data: Object.keys(this.data).reduce((acc, key) => ({
...acc,
[key]: { chomping: '+' },
lineWidth: -1,
[key]: { chomping: '+' },
}), {}),
};
},
Expand Down
38 changes: 38 additions & 0 deletions shell/utils/__tests__/create-yaml.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,42 @@ describe('fx: dumpBlock', () => {
});
});
});

it('should retain line breaks when a line longer than 80 characters exists', () => {
const data = {
'managerApiConfiguration.properties': `# Sample XPlanManagerAPI Configuration (if this comment is longer than 80 characters, the output should remain the same)
apiUrl=https://example.com/xplan-api-manager
contactEmailAddress=contact@example.com
termsOfServiceUrl=https://example.com/terms
documentationUrl=https://example.com/docs
wmsUrl=https://example.com/xplan-wms/services
skipSemantic=false
skipGeometric=true`
};

const expectedResult = `managerApiConfiguration.properties: >+
# Sample XPlanManagerAPI Configuration (if this comment is longer than 80 characters, the output should remain the same)
apiUrl=https://example.com/xplan-api-manager
contactEmailAddress=contact@example.com
termsOfServiceUrl=https://example.com/terms
documentationUrl=https://example.com/docs
wmsUrl=https://example.com/xplan-wms/services
skipSemantic=false
skipGeometric=true
`;

const yamlModifiers = {
lineWidth: -1,
'managerApiConfiguration.properties': {
chomping: '+',
scalarStyle: '>',
}
};

const result = dumpBlock(data, yamlModifiers);

expect(result).toStrictEqual(expectedResult);
});
});
17 changes: 9 additions & 8 deletions shell/utils/create-yaml.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,20 +443,21 @@ export function saferDump(obj) {
*
* this is required since jsyaml.dump doesn't support chomping and scalar style at the moment.
* see: https://github.com/nodeca/js-yaml/issues/171
* @typedef {Object} DumpBlockOptions
* @property {('>' | '|')} [scalarStyle] - The scalar style.
* @property {('-' | '+' | '' | null)} [chomping] - The chomping style.
*
* @param {*} data the multiline block
* @param {*} options blocks indicators, see: https://yaml-multiline.info
* @param {Object} options - Serialization options for jsyaml.dump.
* @param {number} options.lineWidth - Set max line width. Set -1 for unlimited width.
* @param {DumpBlockOptions} [options.dynamicProperties] - Options for dynamic properties.
* Developers can provide their own property names under `options`.
*
* - scalarStyle:
* one of '|', '>'
* default '|'
* - chomping:
* one of: null, '', '-', '+'
* default: null
* @returns the result of jsyaml.dump with the addition of multiline indicators
*/
export function dumpBlock(data, options = {}) {
const parsed = jsyaml.dump(data);
const parsed = jsyaml.dump(data, options);

let out = parsed;

Expand Down

0 comments on commit 0292e11

Please sign in to comment.