Skip to content
Open
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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"browserify-sign": "^4.2.2",
"axios": "^1.8.2",
"braces": "^3.0.3",
"micromatch": "^4.0.8"
"micromatch": "^4.0.8",
"pbkdf2": "^3.1.3",
"**/form-data": "^4.0.4"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import { DataSourceSelectableConfig, } from '../../../../../../src/plugins/data_
import {
constructHrefWithDataSourceId,
getDataSourceFromURL,
isDataSourceCompatible,
isForecastingDataSourceCompatible,
} from '../../utils/helpers';
import queryString from 'querystring';
import { Features } from '../components/Features/Features';
Expand Down Expand Up @@ -307,7 +307,7 @@ export const DefineForecaster = (props: DefineForecasterProps) => {
notifications: getNotifications(),
onSelectedDataSources: (dataSources) =>
handleDataSourceChange(dataSources),
dataSourceFilter: isDataSourceCompatible,
dataSourceFilter: isForecastingDataSourceCompatible,
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ const getPlaceholderWithBadge = (label: string, count: number) => {

// The ratio is now approximately 4:1:2 between search:status:index
export const ListFilters = (props: ListFiltersProps) => {
console.log('ListFilters props:', {
selectedForecasterStates: props.selectedForecasterStates,
stateOptions: getForecasterStateOptions()
});

return (
<EuiFlexGroup gutterSize="s">
Expand All @@ -79,11 +75,9 @@ export const ListFilters = (props: ListFiltersProps) => {
singleSelection={false}
options={(() => {
const options = getForecasterStateOptions();
console.log('State filter options:', options);
return options;
})()}
onChange={(selectedOptions) => {
console.log('State filter onChange called with:', selectedOptions);
props.onForecasterStateChange(selectedOptions);
setTimeout(() => {
console.log('After onForecasterStateChange, props are now:', props.selectedForecasterStates);
Expand Down
4 changes: 2 additions & 2 deletions public/pages/ForecastersList/containers/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import {
getAllForecastersQueryParamsWithDataSourceId,
getDataSourceFromURL,
getVisibleOptions,
isDataSourceCompatible,
isForecastingDataSourceCompatible,
sanitizeSearchText,
} from '../../../utils/helpers';
import { ListFilters } from '../../components/ListFilters/ListFilters';
Expand Down Expand Up @@ -573,7 +573,7 @@ export const ForecastersList = (props: ListProps) => {
notifications: getNotifications(),
onSelectedDataSources: (dataSources) =>
handleDataSourceChange(dataSources),
dataSourceFilter: isDataSourceCompatible,
dataSourceFilter: isForecastingDataSourceCompatible,
}}
/>
);
Expand Down
88 changes: 87 additions & 1 deletion public/pages/utils/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@
* GitHub history for details.
*/

import { getVisibleOptions, groupIndicesOrAliasesByCluster, sanitizeSearchText } from '../helpers';
import {
getVisibleOptions,
groupIndicesOrAliasesByCluster,
isDataSourceCompatible,
isForecastingDataSourceCompatible,
sanitizeSearchText,
} from '../helpers';

jest.mock('../../../../opensearch_dashboards.json', () => ({
supportedOSDataSourceVersions: '>=2.9.0',
requiredOSDataSourcePlugins: ['opensearch-anomaly-detection'],
}));

describe('helpers', () => {
describe('getVisibleOptions', () => {
test('returns without system indices if valid index options and undefined localCluster', () => {
Expand Down Expand Up @@ -203,4 +215,78 @@ describe('helpers', () => {
expect(sanitizeSearchText('hello')).toBe('*hello*');
});
});

describe('isDataSourceCompatible', () => {
const mockDataSource = (
version: string,
plugins: string[] = ['opensearch-anomaly-detection']
) => ({
attributes: {
dataSourceVersion: version,
installedPlugins: plugins,
},
});

test('should be compatible for version 3.1.0', () => {
const dataSource = mockDataSource('3.1.0');
expect(isDataSourceCompatible(dataSource as any)).toBe(true);
});

test('should be compatible for version 2.19.3', () => {
const dataSource = mockDataSource('2.19.3');
expect(isDataSourceCompatible(dataSource as any)).toBe(true);
});

test('should be compatible for version 3.1.0-SNAPSHOT', () => {
const dataSource = mockDataSource('3.1.0-SNAPSHOT');
expect(isDataSourceCompatible(dataSource as any)).toBe(true);
});

test('should be incompatible for version 2.8.0', () => {
const dataSource = mockDataSource('2.8.0');
expect(isDataSourceCompatible(dataSource as any)).toBe(false);
});

test('should be incompatible if required plugin is missing', () => {
const dataSource = mockDataSource('3.1.0', ['some-other-plugin']);
expect(isDataSourceCompatible(dataSource as any)).toBe(false);
});
});

describe('isForecastingDataSourceCompatible', () => {
const mockDataSource = (
version: string,
plugins: string[] = ['opensearch-anomaly-detection']
) => ({
attributes: {
dataSourceVersion: version,
installedPlugins: plugins,
},
});

test('should be compatible for version 3.1.0', () => {
const dataSource = mockDataSource('3.1.0');
expect(isForecastingDataSourceCompatible(dataSource as any)).toBe(true);
});

test('should be compatible for version 3.1.0-SNAPSHOT', () => {
const dataSource = mockDataSource('3.1.0-SNAPSHOT');
expect(isForecastingDataSourceCompatible(dataSource as any)).toBe(true);
});

test('should be incompatible for version 2.19.3', () => {
const dataSource = mockDataSource('2.19.3');
expect(isForecastingDataSourceCompatible(dataSource as any)).toBe(false);
});

test('should be incompatible for version 3.0.0', () => {
const dataSource = mockDataSource('3.0.0');
expect(isForecastingDataSourceCompatible(dataSource as any)).toBe(false);
});

test('should be incompatible if required plugin is missing', () => {
const dataSource = mockDataSource('3.1.0', ['some-other-plugin']);
expect(isForecastingDataSourceCompatible(dataSource as any)).toBe(false);
});
});
});
41 changes: 37 additions & 4 deletions public/pages/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { timeFormatter } from '@elastic/charts';
import { getDataSourceEnabled } from '../../services';
import { DataSourceAttributes } from '../../../../../src/plugins/data_source/common/data_sources';
import { SavedObject } from '../../../../../src/core/public';
import * as pluginManifest from '../../../opensearch_dashboards.json';
import pluginManifest from '../../../opensearch_dashboards.json';
import semver from 'semver';
import _ from 'lodash';

Expand Down Expand Up @@ -287,19 +287,24 @@ export const isDataSourceCompatible = (
dataSource: SavedObject<DataSourceAttributes>
) => {
if (
'requiredOSDataSourcePlugins' in pluginManifest &&
pluginManifest.hasOwnProperty('requiredOSDataSourcePlugins') &&
!pluginManifest.requiredOSDataSourcePlugins.every((plugin) =>
dataSource.attributes.installedPlugins?.includes(plugin)
)
) {
return false;
}

// Remove "-SNAPSHOT" (or any other suffix after a dash) before version check
const normalizedVersion = dataSource.attributes.dataSourceVersion?.includes('-')
? dataSource.attributes.dataSourceVersion.split('-')[0]
: dataSource.attributes.dataSourceVersion;

// filter out data sources which is NOT in the support range of plugin
if (
'supportedOSDataSourceVersions' in pluginManifest &&
pluginManifest.hasOwnProperty('supportedOSDataSourceVersions') &&
!semver.satisfies(
dataSource.attributes.dataSourceVersion,
normalizedVersion,
pluginManifest.supportedOSDataSourceVersions
)
) {
Expand All @@ -308,6 +313,34 @@ export const isDataSourceCompatible = (
return true;
};

export const isForecastingDataSourceCompatible = (
dataSource: SavedObject<DataSourceAttributes>
) => {
// Remove "-SNAPSHOT" (or any other suffix after a dash) before version check
const normalizedVersion = dataSource.attributes.dataSourceVersion?.includes('-')
? dataSource.attributes.dataSourceVersion.split('-')[0]
: dataSource.attributes.dataSourceVersion;

if (
// Note: When importing JSON with `import * as pluginManifest from '.../opensearch_dashboards.json'`,
// the result is a module namespace object: { __esModule: true, default: { ...actual JSON... } }.
// The `in` operator (`'requiredOSDataSourcePlugins' in pluginManifest`) checks only the outer
// namespace object, which does not directly contain the JSON keys—it only has "default" and metadata.
// This will always return false for JSON fields like "requiredOSDataSourcePlugins".
// Using `pluginManifest.hasOwnProperty('requiredOSDataSourcePlugins')` works here because
// `hasOwnProperty` is called on the *actual* object that contains the property. Alternatively,
// unwrap once with `const manifest = pluginManifest.default;` and use the `in` operator on `manifest`.
pluginManifest.hasOwnProperty('supportedOSDataSourceVersions') &&
!semver.satisfies(
normalizedVersion,
">=3.1.0"
)
) {
return false;
}
return isDataSourceCompatible(dataSource);
};

export const getLocalCluster = (clusters: ClusterInfo[]): ClusterInfo[] => {
return clusters.filter((cluster) => cluster.localCluster === true);
};
Expand Down
1 change: 1 addition & 0 deletions test/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
coverageDirectory: './coverage',
moduleNameMapper: {
'\\.(css|less|scss)$': '<rootDir>/test/mocks/styleMock.ts',
'^opensearch-dashboards/public$': '<rootDir>/../../src/core/public',
},
testEnvironment: 'jest-environment-jsdom',
coverageReporters: ['lcov', 'text', 'cobertura'],
Expand Down
Loading
Loading