mirror of https://github.com/rancher/dashboard.git
Merge branch 'master' of github.com:rancher/dashboard into 12485-repositories-disabling-feature
This commit is contained in:
commit
face94e4ff
|
|
@ -7,4 +7,8 @@ export default class FleetClusterList extends BaseResourceList {
|
||||||
details(name: string, index: number) {
|
details(name: string, index: number) {
|
||||||
return this.resourceTable().sortableTable().rowWithName(name).column(index);
|
return this.resourceTable().sortableTable().rowWithName(name).column(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subRows() {
|
||||||
|
return this.resourceTable().sortableTable().subRows();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,8 @@ describe('Fleet Clusters', { tags: ['@fleet', '@adminUser'] }, () => {
|
||||||
rke2ClusterAmazon: {
|
rke2ClusterAmazon: {
|
||||||
clusterName: name,
|
clusterName: name,
|
||||||
namespace,
|
namespace,
|
||||||
}
|
},
|
||||||
|
metadata: { labels: { foo: 'bar' } }
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
removeCluster = true;
|
removeCluster = true;
|
||||||
});
|
});
|
||||||
|
|
@ -123,6 +124,8 @@ describe('Fleet Clusters', { tags: ['@fleet', '@adminUser'] }, () => {
|
||||||
fleetClusterListPage.clusterList().details(clusterName, 4).should('have.text', '1');
|
fleetClusterListPage.clusterList().details(clusterName, 4).should('have.text', '1');
|
||||||
// check resources: testing https://github.com/rancher/dashboard/issues/11154
|
// check resources: testing https://github.com/rancher/dashboard/issues/11154
|
||||||
fleetClusterListPage.clusterList().details(clusterName, 5).contains( ' 1 ', MEDIUM_TIMEOUT_OPT);
|
fleetClusterListPage.clusterList().details(clusterName, 5).contains( ' 1 ', MEDIUM_TIMEOUT_OPT);
|
||||||
|
// check cluster labels
|
||||||
|
fleetClusterListPage.clusterList().subRows().should('contain.text', 'foo=bar');
|
||||||
|
|
||||||
const fleetClusterDetailsPage = new FleetClusterDetailsPo(namespace, clusterName);
|
const fleetClusterDetailsPage = new FleetClusterDetailsPo(namespace, clusterName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export type CreateAmazonRke2ClusterParams = {
|
||||||
type: string,
|
type: string,
|
||||||
clusterName: string,
|
clusterName: string,
|
||||||
namespace: string
|
namespace: string
|
||||||
},
|
},
|
||||||
cloudCredentialsAmazon: {
|
cloudCredentialsAmazon: {
|
||||||
workspace: string,
|
workspace: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -40,7 +40,11 @@ export type CreateAmazonRke2ClusterParams = {
|
||||||
rke2ClusterAmazon: {
|
rke2ClusterAmazon: {
|
||||||
clusterName: string,
|
clusterName: string,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
}
|
},
|
||||||
|
metadata?: {
|
||||||
|
labels?: { [key: string]: string },
|
||||||
|
annotations?: { [key: string]: string },
|
||||||
|
},
|
||||||
}
|
}
|
||||||
export type CreateAmazonRke2ClusterWithoutMachineConfigParams = {
|
export type CreateAmazonRke2ClusterWithoutMachineConfigParams = {
|
||||||
cloudCredentialsAmazon: {
|
cloudCredentialsAmazon: {
|
||||||
|
|
|
||||||
|
|
@ -602,7 +602,9 @@ Cypress.Commands.add('deleteNodeTemplate', (nodeTemplateId, timeout = 30000, fai
|
||||||
* Create RKE2 cluster with Amazon EC2 cloud provider
|
* Create RKE2 cluster with Amazon EC2 cloud provider
|
||||||
*/
|
*/
|
||||||
Cypress.Commands.add('createAmazonRke2Cluster', (params: CreateAmazonRke2ClusterParams) => {
|
Cypress.Commands.add('createAmazonRke2Cluster', (params: CreateAmazonRke2ClusterParams) => {
|
||||||
const { machineConfig, rke2ClusterAmazon, cloudCredentialsAmazon } = params;
|
const {
|
||||||
|
machineConfig, rke2ClusterAmazon, cloudCredentialsAmazon, metadata
|
||||||
|
} = params;
|
||||||
|
|
||||||
return cy.createAwsCloudCredentials(cloudCredentialsAmazon.workspace, cloudCredentialsAmazon.name, cloudCredentialsAmazon.region, cloudCredentialsAmazon.accessKey, cloudCredentialsAmazon.secretKey)
|
return cy.createAwsCloudCredentials(cloudCredentialsAmazon.workspace, cloudCredentialsAmazon.name, cloudCredentialsAmazon.region, cloudCredentialsAmazon.accessKey, cloudCredentialsAmazon.secretKey)
|
||||||
.then((resp: Cypress.Response<any>) => {
|
.then((resp: Cypress.Response<any>) => {
|
||||||
|
|
@ -625,8 +627,12 @@ Cypress.Commands.add('createAmazonRke2Cluster', (params: CreateAmazonRke2Cluster
|
||||||
type: 'provisioning.cattle.io.cluster',
|
type: 'provisioning.cattle.io.cluster',
|
||||||
metadata: {
|
metadata: {
|
||||||
namespace: rke2ClusterAmazon.namespace,
|
namespace: rke2ClusterAmazon.namespace,
|
||||||
annotations: { 'field.cattle.io/description': `${ rke2ClusterAmazon.clusterName }-description` },
|
annotations: {
|
||||||
name: rke2ClusterAmazon.clusterName
|
'field.cattle.io/description': `${ rke2ClusterAmazon.clusterName }-description`,
|
||||||
|
...(metadata?.annotations || {}),
|
||||||
|
},
|
||||||
|
labels: metadata?.labels || {},
|
||||||
|
name: rke2ClusterAmazon.clusterName
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
rkeConfig: {
|
rkeConfig: {
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
"cookie": "0.7.0",
|
"cookie": "0.7.0",
|
||||||
"cookie-universal": "2.2.2",
|
"cookie-universal": "2.2.2",
|
||||||
"cron-validator": "1.2.0",
|
"cron-validator": "1.2.0",
|
||||||
"cronstrue": "1.95.0",
|
"cronstrue": "2.53.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"custom-event-polyfill": "1.0.7",
|
"custom-event-polyfill": "1.0.7",
|
||||||
"d3": "7.3.0",
|
"d3": "7.3.0",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
"core-js": "3.40.0",
|
"core-js": "3.40.0",
|
||||||
"cron-validator": "1.3.1",
|
"cron-validator": "1.3.1",
|
||||||
"cronstrue": "2.50.0",
|
"cronstrue": "2.53.0",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-promise": "5.2.0",
|
"eslint-plugin-promise": "5.2.0",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ describe('component: LabeledInput', () => {
|
||||||
expect(wrapper.emitted('update:value')![0][0]).toBe(value);
|
expect(wrapper.emitted('update:value')![0][0]).toBe(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('using mode "multiline" should emit input value correctly', () => {
|
it('using type "multiline" should emit input value correctly', () => {
|
||||||
const value = 'any-string';
|
const value = 'any-string';
|
||||||
const delay = 1;
|
const delay = 1;
|
||||||
const wrapper = mount(LabeledInput, {
|
const wrapper = mount(LabeledInput, {
|
||||||
|
|
@ -37,4 +37,21 @@ describe('component: LabeledInput', () => {
|
||||||
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
||||||
expect(wrapper.emitted('update:value')![0][0]).toBe(value);
|
expect(wrapper.emitted('update:value')![0][0]).toBe(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('using type "chron"', () => {
|
||||||
|
it.each([
|
||||||
|
['0 * * * *', 'Every hour, every day'],
|
||||||
|
['@daily', 'At 12:00 AM, every day'],
|
||||||
|
['You must fail! Go!', '%generic.invalidCron%'],
|
||||||
|
])('passing value %p should display hint %p', (value, hint) => {
|
||||||
|
const wrapper = mount(LabeledInput, {
|
||||||
|
propsData: { value, type: 'cron' },
|
||||||
|
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
||||||
|
});
|
||||||
|
|
||||||
|
const subLabel = wrapper.find('[data-testid="sub-label"]');
|
||||||
|
|
||||||
|
expect(subLabel.text()).toBe(hint);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -179,14 +179,28 @@ export default defineComponent({
|
||||||
if (this.type !== 'cron' || !this.value) {
|
if (this.type !== 'cron' || !this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - #13202: This is required due use of 2 libraries and 3 different libraries through the code.
|
||||||
|
const predefined = [
|
||||||
|
'@yearly',
|
||||||
|
'@annually',
|
||||||
|
'@monthly',
|
||||||
|
'@weekly',
|
||||||
|
'@daily',
|
||||||
|
'@midnight',
|
||||||
|
'@hourly'
|
||||||
|
];
|
||||||
|
const isPredefined = predefined.includes(this.value as string);
|
||||||
|
|
||||||
// refer https://github.com/GuillaumeRochat/cron-validator#readme
|
// refer https://github.com/GuillaumeRochat/cron-validator#readme
|
||||||
if (!isValidCron(this.value as string, {
|
if (!isPredefined && !isValidCron(this.value as string, {
|
||||||
alias: true,
|
alias: true,
|
||||||
allowBlankDay: true,
|
allowBlankDay: true,
|
||||||
allowSevenAsSunday: true,
|
allowSevenAsSunday: true,
|
||||||
})) {
|
})) {
|
||||||
return this.t('generic.invalidCron');
|
return this.t('generic.invalidCron');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const hint = cronstrue.toString(this.value as string || '', { verbose: true });
|
const hint = cronstrue.toString(this.value as string || '', { verbose: true });
|
||||||
|
|
||||||
|
|
@ -382,6 +396,7 @@ export default defineComponent({
|
||||||
<div
|
<div
|
||||||
v-if="cronHint || subLabel"
|
v-if="cronHint || subLabel"
|
||||||
class="sub-label"
|
class="sub-label"
|
||||||
|
data-testid="sub-label"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="cronHint"
|
v-if="cronHint"
|
||||||
|
|
|
||||||
|
|
@ -2385,6 +2385,9 @@ fleet:
|
||||||
cluster:
|
cluster:
|
||||||
summary: Resource Summary
|
summary: Resource Summary
|
||||||
nonReady: Non-Ready Bundles
|
nonReady: Non-Ready Bundles
|
||||||
|
labels: Labels
|
||||||
|
hideLabels: Show less
|
||||||
|
showLabels: Show more
|
||||||
clusters:
|
clusters:
|
||||||
harvester: |-
|
harvester: |-
|
||||||
There {count, plural,
|
There {count, plural,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
import ResourceTable from '@shell/components/ResourceTable';
|
||||||
|
import Tag from '@shell/components/Tag.vue';
|
||||||
import { STATE, NAME, AGE, FLEET_SUMMARY } from '@shell/config/table-headers';
|
import { STATE, NAME, AGE, FLEET_SUMMARY } from '@shell/config/table-headers';
|
||||||
import { FLEET, MANAGEMENT } from '@shell/config/types';
|
import { FLEET, MANAGEMENT } from '@shell/config/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ResourceTable },
|
components: { ResourceTable, Tag },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
rows: {
|
rows: {
|
||||||
|
|
@ -75,6 +76,12 @@ export default {
|
||||||
pluralLabel: this.$store.getters['type-map/labelFor'](schema, 99),
|
pluralLabel: this.$store.getters['type-map/labelFor'](schema, 99),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleCustomLabels(row) {
|
||||||
|
row['displayCustomLabels'] = !row.displayCustomLabels;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -85,6 +92,7 @@ export default {
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
|
:sub-rows="true"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
||||||
key-field="_key"
|
key-field="_key"
|
||||||
|
|
@ -123,5 +131,78 @@ export default {
|
||||||
:class="{'text-error': !row.bundleInfo.total}"
|
:class="{'text-error': !row.bundleInfo.total}"
|
||||||
>{{ row.bundleInfo.total }}</span>
|
>{{ row.bundleInfo.total }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #sub-row="{fullColspan, row, onRowMouseEnter, onRowMouseLeave}">
|
||||||
|
<tr
|
||||||
|
class="labels-row sub-row"
|
||||||
|
@mouseenter="onRowMouseEnter"
|
||||||
|
@mouseleave="onRowMouseLeave"
|
||||||
|
>
|
||||||
|
<template v-if="row.customLabels.length">
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td :colspan="fullColspan-2">
|
||||||
|
<span
|
||||||
|
v-if="row.customLabels.length"
|
||||||
|
class="mt-5"
|
||||||
|
> {{ t('fleet.cluster.labels') }}:
|
||||||
|
<span
|
||||||
|
v-for="(label, i) in row.customLabels"
|
||||||
|
:key="i"
|
||||||
|
class="mt-5 labels"
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
v-if="i < 7"
|
||||||
|
class="mr-5 label"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</Tag>
|
||||||
|
<Tag
|
||||||
|
v-else-if="i > 6 && row.displayCustomLabels"
|
||||||
|
class="mr-5 label"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</Tag>
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
v-if="row.customLabels.length > 7"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="toggleCustomLabels(row)"
|
||||||
|
>
|
||||||
|
{{ t(`fleet.cluster.${row.displayCustomLabels? 'hideLabels' : 'showLabels'}`) }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
<td
|
||||||
|
v-else
|
||||||
|
:colspan="fullColspan"
|
||||||
|
>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
</ResourceTable>
|
</ResourceTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.labels-row {
|
||||||
|
td {
|
||||||
|
padding-top:0;
|
||||||
|
.tag {
|
||||||
|
margin-right: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.labels {
|
||||||
|
display: inline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { LOCAL_CLUSTER, MANAGEMENT, NORMAN } from '@shell/config/types';
|
import { LOCAL_CLUSTER, MANAGEMENT, NORMAN } from '@shell/config/types';
|
||||||
import { CAPI, FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
|
import { CAPI, FLEET as FLEET_LABELS, SYSTEM_LABELS } from '@shell/config/labels-annotations';
|
||||||
import { _RKE2 } from '@shell/store/prefs';
|
import { _RKE2 } from '@shell/store/prefs';
|
||||||
import SteveModel from '@shell/plugins/steve/steve-class';
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
||||||
import { escapeHtml } from '@shell/utils/string';
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
|
|
@ -190,6 +190,22 @@ export default class FleetCluster extends SteveModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get customLabels() {
|
||||||
|
const parsedLabels = [];
|
||||||
|
|
||||||
|
if (this.labels) {
|
||||||
|
for (const k in this.labels) {
|
||||||
|
const [prefix] = k.split('/');
|
||||||
|
|
||||||
|
if (!SYSTEM_LABELS.includes(prefix) && k !== CAPI.PROVIDER) {
|
||||||
|
parsedLabels.push(`${ k }=${ this.labels[k] }`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedLabels;
|
||||||
|
}
|
||||||
|
|
||||||
async saveYaml(yaml) {
|
async saveYaml(yaml) {
|
||||||
await this._saveYaml(yaml);
|
await this._saveYaml(yaml);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
"cookie": "0.7.0",
|
"cookie": "0.7.0",
|
||||||
"core-js": "3.40.0",
|
"core-js": "3.40.0",
|
||||||
"cron-validator": "1.3.1",
|
"cron-validator": "1.3.1",
|
||||||
"cronstrue": "2.50.0",
|
"cronstrue": "2.53.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"css-loader": "6.7.3",
|
"css-loader": "6.7.3",
|
||||||
"csv-loader": "3.0.3",
|
"csv-loader": "3.0.3",
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,8 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
||||||
// Check if the API supports filtering by this field
|
// Check if the API supports filtering by this field
|
||||||
this.validateField(validateFields, schema, field.field);
|
this.validateField(validateFields, schema, field.field);
|
||||||
|
|
||||||
const exactPartial = field.exact ? `'${ field.value }'` : field.value;
|
const value = encodeURIComponent(field.value);
|
||||||
|
const exactPartial = field.exact ? `'${ value }'` : value;
|
||||||
|
|
||||||
return `${ this.convertArrayPath(field.field) }${ field.equals ? '=' : '!=' }${ exactPartial }`;
|
return `${ this.convertArrayPath(field.field) }${ field.equals ? '=' : '!=' }${ exactPartial }`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ import cronstrue from 'cronstrue';
|
||||||
|
|
||||||
export function cronSchedule(schedule = '', getters, errors) {
|
export function cronSchedule(schedule = '', getters, errors) {
|
||||||
try {
|
try {
|
||||||
cronstrue.toString(schedule, { verbose: true });
|
cronScheduleRule.validation(schedule);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.push(getters['i18n/t']('validation.invalidCron'));
|
errors.push(getters['i18n/t'](cronScheduleRule.message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const cronScheduleRule = {
|
||||||
|
validation: (text) => cronstrue.toString(text, { verbose: true }),
|
||||||
|
message: 'validation.invalidCron'
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -27,22 +27,6 @@ describe('formRules', () => {
|
||||||
expect(formRuleResult).toStrictEqual(expectedResult);
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('"cronSchedule" : returns undefined when valid cron string value supplied', () => {
|
|
||||||
const testValue = '0 * * * *';
|
|
||||||
const formRuleResult = formRules.cronSchedule(testValue);
|
|
||||||
|
|
||||||
expect(formRuleResult).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('"cronSchedule" : returns the correct message when invalid cron string value supplied', () => {
|
|
||||||
// specific logic of what constitutes a cron string is in the "cronstrue" function in an external library and not tested here
|
|
||||||
const testValue = '0 * * **';
|
|
||||||
const formRuleResult = formRules.cronSchedule(testValue);
|
|
||||||
const expectedResult = JSON.stringify({ message: 'validation.invalidCron' });
|
|
||||||
|
|
||||||
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('"https" : returns undefined when valid https url value is supplied', () => {
|
it('"https" : returns undefined when valid https url value is supplied', () => {
|
||||||
const testValue = 'https://url.com';
|
const testValue = 'https://url.com';
|
||||||
const formRuleResult = formRules.https(testValue);
|
const formRuleResult = formRules.https(testValue);
|
||||||
|
|
@ -1112,6 +1096,13 @@ describe('formRules', () => {
|
||||||
expect(formRuleResult).toStrictEqual(expectedResult);
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test all factory validators
|
||||||
|
* @param rule - the name of the factory validator
|
||||||
|
* @param argument - the value to validate
|
||||||
|
* @param correctValues - an array of values that should pass the validation
|
||||||
|
* @param wrongValues - an array of values that should fail the validation
|
||||||
|
*/
|
||||||
describe.each([
|
describe.each([
|
||||||
['minValue', 2, [3], [1]],
|
['minValue', 2, [3], [1]],
|
||||||
['maxValue', 256, [1], [300]],
|
['maxValue', 256, [1], [300]],
|
||||||
|
|
@ -1133,12 +1124,18 @@ describe('formRules', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test all standard validators
|
||||||
|
* @param rule - the name of the standard validator
|
||||||
|
* @param correctValues - an array of values that should pass the validation
|
||||||
|
* @param wrongValues - an array of values that should fail the validation
|
||||||
|
*/
|
||||||
describe.each([
|
describe.each([
|
||||||
['requiredInt', [2, 2.2], ['e']],
|
['requiredInt', [2, 2.2], ['e']],
|
||||||
['isInteger', ['2', 2, 0], [2.2, 'e', '1.0']],
|
['isInteger', ['2', 2, 0], [2.2, 'e', '1.0']],
|
||||||
['isPositive', ['0', 1], [-1]],
|
['isPositive', ['0', 1], [-1]],
|
||||||
['isOctal', ['0', 0, 10], ['01']],
|
['isOctal', ['0', 0, 10], ['01']],
|
||||||
|
['cronSchedule', ['0 * * * *', '@daily'], ['0 * * **']],
|
||||||
])('given validator %p', (rule, correctValues, wrongValues) => {
|
])('given validator %p', (rule, correctValues, wrongValues) => {
|
||||||
it.each(wrongValues as [])('should return error for value %p', (wrong) => {
|
it.each(wrongValues as [])('should return error for value %p', (wrong) => {
|
||||||
const formRuleResult = (formRules as any)[rule](wrong);
|
const formRuleResult = (formRules as any)[rule](wrong);
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,26 @@ import isEmpty from 'lodash/isEmpty';
|
||||||
import has from 'lodash/has';
|
import has from 'lodash/has';
|
||||||
import isUrl from 'is-url';
|
import isUrl from 'is-url';
|
||||||
// import uniq from 'lodash/uniq';
|
// import uniq from 'lodash/uniq';
|
||||||
import cronstrue from 'cronstrue';
|
|
||||||
import { Translation } from '@shell/types/t';
|
import { Translation } from '@shell/types/t';
|
||||||
import { isHttps, isLocalhost, hasTrailingForwardSlash } from '@shell/utils/validators/setting';
|
import { isHttps, isLocalhost, hasTrailingForwardSlash } from '@shell/utils/validators/setting';
|
||||||
|
import { cronScheduleRule } from '@shell/utils/validators/cron-schedule';
|
||||||
|
|
||||||
// import uniq from 'lodash/uniq';
|
// import uniq from 'lodash/uniq';
|
||||||
export type Validator<T = undefined | string> = (val: any, arg?: any) => T;
|
|
||||||
|
|
||||||
export type ValidatorFactory = (arg1: any, arg2?: any) => Validator
|
/**
|
||||||
|
* Fixed validation rule which require only the value to be evaluated
|
||||||
|
* @param value
|
||||||
|
* @returns { string | undefined }
|
||||||
|
*/
|
||||||
|
export type Validator<T = undefined | string> = (value: any, arg?: any) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function which returns a validation rule
|
||||||
|
* @param arg Argument used as part of the validation rule process, not necessarily as parameter of the validation rule
|
||||||
|
* @param value Value to be evaluated
|
||||||
|
* @returns { Validator }
|
||||||
|
*/
|
||||||
|
export type ValidatorFactory = (arg: any, value?: any) => Validator
|
||||||
|
|
||||||
type ServicePort = {
|
type ServicePort = {
|
||||||
name?: string,
|
name?: string,
|
||||||
|
|
@ -131,9 +143,9 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
||||||
|
|
||||||
const cronSchedule: Validator = (val: string) => {
|
const cronSchedule: Validator = (val: string) => {
|
||||||
try {
|
try {
|
||||||
cronstrue.toString(val, { verbose: true });
|
cronScheduleRule.validation(val);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return t('validation.invalidCron');
|
return t(cronScheduleRule.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,23 @@ Input elements with type ‘text’ allow users to enter any combination of lett
|
||||||
#### Error
|
#### Error
|
||||||
|
|
||||||
<Canvas of={LabeledInput.Error} />
|
<Canvas of={LabeledInput.Error} />
|
||||||
|
|
||||||
|
#### SubLabel
|
||||||
|
|
||||||
|
Addition information can be added to the input field using the `subLabel` prop.
|
||||||
|
|
||||||
|
<Canvas of={LabeledInput.SubLabel} />
|
||||||
|
|
||||||
|
#### Cron Type
|
||||||
|
|
||||||
|
The `cron` prop can be used to add [Cron language](https://en.wikipedia.org/wiki/Cron) hints.
|
||||||
|
|
||||||
|
<Canvas of={LabeledInput.CronType} />
|
||||||
|
|
||||||
|
<Canvas of={LabeledInput.CronTypeDaily} />
|
||||||
|
|
||||||
|
<Canvas of={LabeledInput.CronTypeError} />
|
||||||
|
|
||||||
|
#### Multiline Type
|
||||||
|
|
||||||
|
<Canvas of={LabeledInput.MultilineType} />
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,53 @@ export const Error: Story = {
|
||||||
tooltipKey: 'Error message'
|
tooltipKey: 'Error message'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SubLabel: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
label: 'Name',
|
||||||
|
subLabel: 'Additional information',
|
||||||
|
type: 'text',
|
||||||
|
value: 'Simon',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CronType: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
label: 'Period',
|
||||||
|
type: 'cron',
|
||||||
|
value: '0 * * * *',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CronTypeDaily: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
label: 'Period',
|
||||||
|
type: 'cron',
|
||||||
|
value: '@daily',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CronTypeError: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
label: 'Period',
|
||||||
|
type: 'cron',
|
||||||
|
value: 'not a cron expression',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultilineType: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
label: 'Period',
|
||||||
|
type: 'multiline',
|
||||||
|
value: `this
|
||||||
|
is
|
||||||
|
a
|
||||||
|
multiline
|
||||||
|
text`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
||||||
13
yarn.lock
13
yarn.lock
|
|
@ -6386,15 +6386,10 @@ cron-validator@1.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.3.1.tgz#8f2fe430f92140df77f91178ae31fc1e3a48a20e"
|
resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.3.1.tgz#8f2fe430f92140df77f91178ae31fc1e3a48a20e"
|
||||||
integrity sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==
|
integrity sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==
|
||||||
|
|
||||||
cronstrue@1.95.0:
|
cronstrue@2.53.0:
|
||||||
version "1.95.0"
|
version "2.53.0"
|
||||||
resolved "https://registry.npmjs.org/cronstrue/-/cronstrue-1.95.0.tgz#171df1fad8b0f0cb636354dd1d7842161c15478f"
|
resolved "https://registry.npmjs.org/cronstrue/-/cronstrue-2.53.0.tgz#5bbcd7483636b99379480f624faef5056f3efbd8"
|
||||||
integrity sha512-CdbQ17Z8Na2IdrK1SiD3zmXfE66KerQZ8/iApkGsxjmUVGJPS9M9oK4FZC3LM6ohUjjq3UeaSk+90Cf3QbXDfw==
|
integrity sha512-CkAcaI94xL8h6N7cGxgXfR5D7oV2yVtDzB9vMZP8tIgPyEv/oc/7nq9rlk7LMxvc3N+q6LKZmNLCVxJRpyEg8A==
|
||||||
|
|
||||||
cronstrue@2.50.0:
|
|
||||||
version "2.50.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.50.0.tgz#eabba0f915f186765258b707b7a3950c663b5573"
|
|
||||||
integrity sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==
|
|
||||||
|
|
||||||
cross-env@7.0.3:
|
cross-env@7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue