Compare commits

..
3 Commits
Author SHA1 Message Date
allan d13fab267f Merge branch 'additional-validations-regex' into regex-info
Build / Build-and-ng-test (pull_request) Successful in 5m35s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m51s
Build / Build-and-test-development (pull_request) Successful in 20m2s
2026-07-25 18:17:36 +00:00
allan aaf406b386 Merge branch 'additional-validations-regex' into regex-info
Build / Build-and-ng-test (pull_request) Successful in 4m58s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m51s
Build / Build-and-test-development (pull_request) Successful in 19m50s
2026-07-24 15:43:45 +00:00
YuryShkoda 39c8855f37 feat(editor): show applied HARDREGEX/SOFTREGEX pattern in column info dropdown
Build / Build-and-ng-test (pull_request) Failing after 1m44s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 21m25s
Add DcValidator.getRegexRuleValue (HARDREGEX takes precedence), thread it
through buildColInfoHtml, and cover it with a Cypress test.
2026-07-24 17:15:42 +03:00
6 changed files with 166 additions and 3 deletions
+43
View File
@@ -264,6 +264,31 @@ context('editor tests: ', function () {
})
})
})
it('9 | Info dropdown shows the applied HARDREGEX/SOFTREGEX pattern', () => {
openTableFromTree(libraryToOpenIncludes, 'mpe_x_new')
clickOnEdit(() => {
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
timeout: longerCommandTimeout
}).then(() => {
scrollGridRight()
openColumnDropdown('REGEX_HARD_COL')
cy.get('.htDropdownMenu').should(($menu) => {
expect($menu.text()).to.include('REGEX: /[\\w.]+@[\\w]+\\.[a-z]{2,}/')
})
cy.get('body').click(0, 0) // close menu
openColumnDropdown('REGEX_SOFT_COL')
cy.get('.htDropdownMenu').should(($menu) => {
expect($menu.text()).to.include(
'REGEX: /[A-Z]{1,2}\\d{1,2}[A-Z]?\\s?\\d[A-Z]{2}/'
)
})
})
})
})
})
// Handsontable virtualizes columns — with 17 columns on MPE_X_NEW, only
@@ -280,6 +305,24 @@ const scrollGridRight = () => {
.scrollTo('right')
}
// Opens a column header's dropdown menu to reach its `info` item, which
// has a custom renderer showing NAME/LABEL/TYPE/LENGTH/FORMAT and, when the
// column has a HARDREGEX/SOFTREGEX rule, the applied pattern. Clicking the
// header text first selects the column - the renderer reads
// hot.getSelected() to decide which column to describe.
const openColumnDropdown = (headerText: string) => {
cy.get('#hotTable .ht_clone_top .htCore thead button.changeType', {
timeout: longerCommandTimeout
})
.parents('th')
.filter((_, th) => Cypress.$(th).text().includes(headerText))
.last()
.as('targetHeader')
cy.get('@targetHeader').click()
cy.get('@targetHeader').find('button.changeType').click({ force: true })
}
// Locates a body cell by its column's header text rather than a hardcoded
// childNodes index. Handsontable's hiddenColumns plugin (e.g. HIDDEN_COL,
// used by several demo columns ahead of REGEX_HARD_COL/REGEX_SOFT_COL in
+5 -1
View File
@@ -3254,7 +3254,11 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
colName = this.hotInstance?.colToProp(selectedCol) as string
colInfo = this.$dataFormats?.vars[colName]
textInfo = buildColInfoHtml(colName, colInfo)
textInfo = buildColInfoHtml(
colName,
colInfo,
this.dcValidator?.getRegexRuleValue(colName)
)
}
elem.innerHTML = textInfo
@@ -204,6 +204,29 @@ export class DcValidator {
return isNaN(digits) ? undefined : digits
}
/**
* Returns the RULE_VALUE of a HARDREGEX/SOFTREGEX rule on the given
* column, for display in the column-header info dropdown. HARDREGEX wins
* when a column has both, matching failsSoftRegex's own precedence (no
* point surfacing the non-blocking pattern when the blocking one is what
* actually governs the column).
*
* @param col column name
*/
getRegexRuleValue(col: string): string | undefined {
const regexRule =
this.dqrules.find(
(rule: DQRule) =>
rule.BASE_COL === col && rule.RULE_TYPE === 'HARDREGEX'
) ??
this.dqrules.find(
(rule: DQRule) =>
rule.BASE_COL === col && rule.RULE_TYPE === 'SOFTREGEX'
)
return regexRule?.RULE_VALUE
}
/**
* Retrieves dropdown source for given dc validation rule
* The values comes from MPE_SELECTBOX table
@@ -734,6 +734,70 @@ describe('DC Validator', () => {
).toBeFalse()
})
})
describe('14 | getRegexRuleValue (column-header info display)', () => {
const buildValidator = (dqRules: DQRule[]) =>
new DcValidator(
example_sasparams,
example_dataformats,
example_cols,
dqRules,
example_dqData
)
it('returns the RULE_VALUE for a HARDREGEX rule', () => {
const dcValidator = buildValidator([
{
BASE_COL: 'SOME_CHAR_ANY',
RULE_TYPE: 'HARDREGEX',
RULE_VALUE: '^[A-Z]+$',
X: 0
}
])
expect(dcValidator.getRegexRuleValue('SOME_CHAR_ANY')).toEqual('^[A-Z]+$')
})
it('returns the RULE_VALUE for a SOFTREGEX rule', () => {
const dcValidator = buildValidator([
{
BASE_COL: 'SOME_CHAR_ANY',
RULE_TYPE: 'SOFTREGEX',
RULE_VALUE: '/\\b(the|data)\\b/i',
X: 0
}
])
expect(dcValidator.getRegexRuleValue('SOME_CHAR_ANY')).toEqual(
'/\\b(the|data)\\b/i'
)
})
it('prefers HARDREGEX over SOFTREGEX when a column has both', () => {
const dcValidator = buildValidator([
{
BASE_COL: 'SOME_CHAR_ANY',
RULE_TYPE: 'HARDREGEX',
RULE_VALUE: '^HARD$',
X: 0
},
{
BASE_COL: 'SOME_CHAR_ANY',
RULE_TYPE: 'SOFTREGEX',
RULE_VALUE: '^SOFT$',
X: 0
}
])
expect(dcValidator.getRegexRuleValue('SOME_CHAR_ANY')).toEqual('^HARD$')
})
it('returns undefined for a column with no regex rule', () => {
const dcValidator = buildValidator([])
expect(dcValidator.getRegexRuleValue('SOME_CHAR_ANY')).toBeUndefined()
})
})
})
/** Minimal cols[] entry — only the fields rule ordering depends on. */
@@ -18,4 +18,28 @@ describe('buildColInfoHtml', () => {
'NAME: SOME_CHAR<br>LABEL: Some Character Column<br>TYPE: char<br>LENGTH: 1024<br>FORMAT: $1024.'
)
})
it('appends a REGEX line when a regex rule value is provided', () => {
const colInfo: DataFormat = {
label: 'Some Character Column',
type: 'char',
length: '1024',
format: '$1024.'
}
expect(buildColInfoHtml('SOME_CHAR', colInfo, '/^[A-Z]+$/i')).toBe(
'NAME: SOME_CHAR<br>LABEL: Some Character Column<br>TYPE: char<br>LENGTH: 1024<br>FORMAT: $1024.<br>REGEX: /^[A-Z]+$/i'
)
})
it('omits the REGEX line when no regex rule value is provided', () => {
const colInfo: DataFormat = {
label: 'Some Character Column',
type: 'char',
length: '1024',
format: '$1024.'
}
expect(buildColInfoHtml('SOME_CHAR', colInfo)).not.toContain('REGEX:')
})
})
+7 -2
View File
@@ -7,9 +7,14 @@ import { DataFormat } from '../../models/sas/common/DateFormat'
*/
export function buildColInfoHtml(
colName: string,
colInfo?: DataFormat
colInfo?: DataFormat,
regexRuleValue?: string
): string {
if (!colInfo) return 'No info found'
return `NAME: ${colName}<br>LABEL: ${colInfo.label}<br>TYPE: ${colInfo.type}<br>LENGTH: ${colInfo.length}<br>FORMAT: ${colInfo.format}`
let html = `NAME: ${colName}<br>LABEL: ${colInfo.label}<br>TYPE: ${colInfo.type}<br>LENGTH: ${colInfo.length}<br>FORMAT: ${colInfo.format}`
if (regexRuleValue) html += `<br>REGEX: ${regexRuleValue}`
return html
}