Skip to content

Commit 7a95002

Browse files
author
Bewaji Olumuyiwa
committed
Merged PR 9452212: Added support for filtering using capabilities files in PTM Service
## Pull Request Checklist ### General - [ ] Are all regression test passed? - [ ] Are there any test cases that will expose unfixed TDIs or Windows bugs? ### New Test Case - [ ] Have Design Spec and User Guide been updated? - [ ] Can all the test cases be loaded and executed by PTM & PTMCli? - [ ] Can the related changes support multiple platform(Windows, Linux, MacOS)? ### SDK Changes - [ ] Are all related test suites Regression passed? Added support for filtering using capabilities files in PTM Service
1 parent de88f31 commit 7a95002

File tree

8 files changed

+656
-218
lines changed

8 files changed

+656
-218
lines changed

ProtocolTestManager/PTMService/PTMService/ClientApp/src/actions/FilterTestCaseAction.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ export const SET_RULES_FAILURE = 'FILTERTESTCASE/SET_RULES_FAILURE'
2020

2121
export const SET_SELECTED_RULES = 'FILTERTESTCASE/SET_SELECTED_RULES'
2222

23+
export const SELECT_CAPABILITIES_FILE = 'FILTERTESTCASE/SELECT_CAPABILITIES_FILE'
24+
export const SELECT_CAPABILITIES_FILE_CATEGORIES = 'FILTERTESTCASE/SELECT_CAPABILITIES_FILE_CATEGORIES'
25+
export const EXPAND_CAPABILITIES_FILE_CATEGORIES = 'FILTERTESTCASE/EXPAND_CAPABILITIES_FILE_CATEGORIES'
26+
27+
export const GET_CAPABILITIES_CONFIG_REQUEST = 'FILTERTESTCASE/GET_CAPABILITIES_CONFIG_REQUEST'
28+
export const GET_CAPABILITIES_CONFIG_SUCCESS = 'FILTERTESTCASE/GET_CAPABILITIES_CONFIG_SUCCESS'
29+
export const GET_CAPABILITIES_CONFIG_FAILURE = 'FILTERTESTCASE/GET_CAPABILITIES_CONFIG_FAILURE'
30+
2331
// define action types
2432
interface GetTSRulesActionRequestType { type: typeof GET_FILTERTESTCASE_RULES_REQUEST }
2533
interface GetTSRulesActionSuccessType { type: typeof GET_FILTERTESTCASE_RULES_SUCCESS, payload: RuleData }
@@ -35,6 +43,14 @@ interface SetTSRulesActionFailureType { type: typeof SET_RULES_FAILURE, errorMsg
3543

3644
interface SetSelectedRulesActionType { type: typeof SET_SELECTED_RULES, payload: SelectedRuleGroup }
3745

46+
interface SelectCapabilitiesFileActionType { type: typeof SELECT_CAPABILITIES_FILE, payload: number }
47+
interface SelectCapabilitiesFileCategoriesActionType { type: typeof SELECT_CAPABILITIES_FILE_CATEGORIES, payload: string[] }
48+
interface ExpandCapabilitiesFileCategoriesActionType { type: typeof EXPAND_CAPABILITIES_FILE_CATEGORIES, payload: string[] }
49+
50+
interface GetCapabilitiesConfigActionRequestType { type: typeof GET_CAPABILITIES_CONFIG_REQUEST }
51+
interface GetCapabilitiesConfigActionSuccessType { type: typeof GET_CAPABILITIES_CONFIG_SUCCESS, payload: string }
52+
interface GetCapabilitiesConfigActionFailureType { type: typeof GET_CAPABILITIES_CONFIG_FAILURE, errorMsg: string }
53+
3854
export type FilterTestCaseActionTypes = GetTSRulesActionRequestType
3955
| GetTSRulesActionSuccessType
4056
| GetTSRulesActionFailureType
@@ -45,6 +61,12 @@ export type FilterTestCaseActionTypes = GetTSRulesActionRequestType
4561
| SetTSRulesActionSuccessType
4662
| SetTSRulesActionFailureType
4763
| SetSelectedRulesActionType
64+
| SelectCapabilitiesFileActionType
65+
| SelectCapabilitiesFileCategoriesActionType
66+
| ExpandCapabilitiesFileCategoriesActionType
67+
| GetCapabilitiesConfigActionRequestType
68+
| GetCapabilitiesConfigActionSuccessType
69+
| GetCapabilitiesConfigActionFailureType
4870

4971
// define actions
5072
export const FilterTestCaseActions = {
@@ -103,5 +125,40 @@ export const FilterTestCaseActions = {
103125
type: SET_SELECTED_RULES,
104126
payload: info
105127
}
128+
},
129+
selectCapabilitiesFileAction: (id: number): FilterTestCaseActionTypes => {
130+
return {
131+
type: SELECT_CAPABILITIES_FILE,
132+
payload: id
133+
}
134+
},
135+
selectCapabilitiesFileCategoriesAction: (categories: string[]): FilterTestCaseActionTypes => {
136+
return {
137+
type: SELECT_CAPABILITIES_FILE_CATEGORIES,
138+
payload: categories
139+
}
140+
},
141+
expandCapabilitiesFileCategoriesAction: (categories: string[]): FilterTestCaseActionTypes => {
142+
return {
143+
type: EXPAND_CAPABILITIES_FILE_CATEGORIES,
144+
payload: categories
145+
}
146+
},
147+
getCapabilitiesConfigAction_Request: (): FilterTestCaseActionTypes => {
148+
return {
149+
type: GET_CAPABILITIES_CONFIG_REQUEST
150+
}
151+
},
152+
getCapabilitiesConfigAction_Success: (capabilitiesConfigJson: string): FilterTestCaseActionTypes => {
153+
return {
154+
type: GET_CAPABILITIES_CONFIG_SUCCESS,
155+
payload: capabilitiesConfigJson
156+
}
157+
},
158+
getCapabilitiesConfigAction_Failure: (error: string): FilterTestCaseActionTypes => {
159+
return {
160+
type: GET_CAPABILITIES_CONFIG_FAILURE,
161+
errorMsg: error
162+
}
106163
}
107164
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
import { FunctionComponent, useCallback, useState, useEffect, useRef } from 'react'
5+
import CheckboxTree, { Node } from 'react-checkbox-tree'
6+
import { Icon } from '@fluentui/react/lib/Icon'
7+
import { ConfigGroup, ConfigCategory } from '../model/CapabilitiesFileInfo'
8+
9+
interface CapabilitiesTreePanelProps {
10+
groups: ConfigGroup[]
11+
selectedCategories: string[]
12+
expandedCategories: string[]
13+
onChecked: (values: string[]) => void
14+
onExpanded: (values: string[]) => void
15+
}
16+
17+
const mapCategoryToNode = (group: ConfigGroup, category: ConfigCategory) => {
18+
return {
19+
value: `${group.Name.toLowerCase()}.${category.Name.toLowerCase()}`, label: category.Name, children: null, showCheckbox: true
20+
}
21+
}
22+
23+
const mapGroupToNode = (group: ConfigGroup) => {
24+
const children = group.Categories.map(c => mapCategoryToNode(group, c))
25+
26+
return {
27+
value: `(${group.Name.toLowerCase()})`, label: group.Name, children: children, showCheckbox: true
28+
}
29+
}
30+
31+
const createNodes = (groups: ConfigGroup[]) => {
32+
const nodes = groups.map(g => mapGroupToNode(g))
33+
34+
return [{ value: '(All)', label: '(All)', children: nodes, showCheckbox: true }]
35+
}
36+
37+
export const CapabilitiesTreePanel: FunctionComponent<CapabilitiesTreePanelProps> = (props) => {
38+
const data: any = createNodes(props.groups)
39+
const [checked, setChecked] = useState<string[]>(props.selectedCategories)
40+
41+
// UseEffect here helps keeps 'checked' in sync with 'props.selectedCategories'.
42+
// When it's re-rendered in quick succession, useState doesn't update on the re-render.
43+
useEffect(() => {
44+
setChecked(props.selectedCategories)
45+
}, [props.selectedCategories])
46+
47+
const [expanded, setExpanded] = useState<string[]>(props.expandedCategories ?? ['(All)'])
48+
49+
const trackChecked = (checked: string[], onChecked: (values: string[]) => void) => {
50+
setChecked(checked)
51+
onChecked(checked)
52+
}
53+
54+
const trackExpanded = (expanded: string[], onExpanded: (values: string[]) => void) => {
55+
setExpanded(expanded)
56+
onExpanded(expanded)
57+
}
58+
59+
return (
60+
props.groups.length > 0
61+
? <div style={{ border: '1px solid rgba(0, 0, 0, 0.35)', padding: '5px' }}>
62+
<CheckboxTree nodes={data}
63+
checked={checked}
64+
expanded={expanded}
65+
onClick={curr => { }}
66+
onCheck={curr => trackChecked(curr, props.onChecked)}
67+
onExpand={expanded => trackExpanded(expanded, props.onExpanded)}
68+
showNodeIcon={false}
69+
icons={{
70+
check: <Icon aria-label='Checked' iconName="CheckboxComposite" />,
71+
uncheck: <Icon aria-label='Not checked' iconName="Checkbox" />,
72+
halfCheck: <i aria-label='Partially checked' role='img' className="ms-Icon ms-Icon--CheckboxIndeterminateCombo" />,
73+
expandClose: <Icon aria-label='Collapsed' iconName="CaretHollow" />,
74+
expandOpen: <Icon aria-label='Expanded' iconName="CaretSolid" />
75+
}} />
76+
</div>
77+
: <div>
78+
<span style={{ fontSize: '13px', borderTop: '5px', borderLeft: '5px' }}>There are no categories defined in the selected file.</span>
79+
</div>
80+
)
81+
}

ProtocolTestManager/PTMService/PTMService/ClientApp/src/model/CapabilitiesFileInfo.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
import { TestCase } from "./TestCase"
5+
46
export type TestCasesViewType = 'InCategory' | 'OutCategory' | 'NoCategory' | undefined
57

68
export const TestCasesViewsConfig = new Map<string, TestCasesViewType>([
@@ -58,3 +60,8 @@ export interface CapabilitiesConfig {
5860
TestCases: ConfigTestCase[]
5961
TestCasesMap: Map<string, ConfigTestCase>
6062
}
63+
64+
export interface TestCaseWithCategories {
65+
Test: TestCase
66+
LowerCaseCategories: Set<string>
67+
}

ProtocolTestManager/PTMService/PTMService/ClientApp/src/pages/FilterTestCase.tsx

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,74 +10,131 @@ import { getNavSteps, RunSteps } from '../model/DefaultNavSteps'
1010
import { useDispatch, useSelector } from 'react-redux'
1111
import { AppState } from '../store/configureStore'
1212
import { RuleListPanel } from '../components/RuleListPanel'
13+
import { CapabilitiesTreePanel } from '../components/CapabilitiesTreePanel'
1314
import { ConfigurationsDataSrv } from '../services/Configurations'
1415
import { FilterTestCaseActions } from '../actions/FilterTestCaseAction'
1516
import { TestSuitesDataSrv } from '../services/TestSuites'
1617
import { SelectedRuleGroup } from '../model/RuleGroup'
17-
import { IStackItemTokens, IStackTokens, PrimaryButton, Stack } from '@fluentui/react'
18+
import { IStackItemTokens, IStackTokens, PrimaryButton, Stack, Dropdown, Label } from '@fluentui/react'
1819
import { ConfigurationMethod_AutoDetection } from './ConfigureMethod'
1920
import { PropertyGroupsActions } from '../actions/PropertyGroupsAction'
2021
import { InvalidAppStateNotification } from '../components/InvalidAppStateNotification'
22+
import { CapabilitiesDataSrv } from '../services/Capabilities'
2123

2224
export const FilterTestCase: React.FC<any> = (props: any) => {
23-
const wizardProps: StepWizardChildProps = props as StepWizardChildProps
25+
const wizardProps: StepWizardChildProps = props as StepWizardChildProps
2426

25-
const dispatch = useDispatch()
26-
const testSuiteInfo = useSelector((state: AppState) => state.testSuiteInfo)
27-
const configuration = useSelector((state: AppState) => state.configurations)
28-
const filterInfo = useSelector((state: AppState) => state.filterInfo)
29-
const configureMethod = useSelector((state: AppState) => state.configureMethod)
30-
const detectionResult = useSelector((state: AppState) => state.detectResult)
27+
const dispatch = useDispatch()
28+
const testSuiteInfo = useSelector((state: AppState) => state.testSuiteInfo)
29+
const configuration = useSelector((state: AppState) => state.configurations)
30+
const filterInfo = useSelector((state: AppState) => state.filterInfo)
31+
const configureMethod = useSelector((state: AppState) => state.configureMethod)
32+
const detectionResult = useSelector((state: AppState) => state.detectResult)
33+
const capabilitiesListState = useSelector((state: AppState) => state.capabilitiesList)
3134

32-
const navSteps = getNavSteps(wizardProps, configureMethod)
33-
const wizard = WizardNavBar(wizardProps, navSteps)
35+
const navSteps = getNavSteps(wizardProps, configureMethod)
36+
const wizard = WizardNavBar(wizardProps, navSteps)
3437

35-
useEffect(() => {
36-
dispatch(ConfigurationsDataSrv.getRules())
37-
dispatch(TestSuitesDataSrv.getTestSuiteTestCases())
38-
}, [dispatch])
38+
useEffect(() => {
39+
dispatch(ConfigurationsDataSrv.getRules())
40+
dispatch(TestSuitesDataSrv.getTestSuiteTestCases())
41+
dispatch(CapabilitiesDataSrv.getCapabilitiesFiles())
42+
}, [dispatch])
3943

40-
if (testSuiteInfo.selectedTestSuite === undefined || configuration.selectedConfiguration === undefined) {
41-
return <InvalidAppStateNotification
44+
if (testSuiteInfo.selectedTestSuite === undefined || configuration.selectedConfiguration === undefined) {
45+
return <InvalidAppStateNotification
4246
testSuite={testSuiteInfo.selectedTestSuite}
4347
configuration={configuration.selectedConfiguration}
4448
wizard={wizard}
4549
wizardProps={wizardProps} />
50+
}
51+
52+
const onPreviousButtonClick: React.MouseEventHandler<unknown> = () => {
53+
if (configureMethod?.selectedMethod === ConfigurationMethod_AutoDetection && detectionResult.detectionResult !== undefined) {
54+
wizardProps.previousStep()
55+
} else {
56+
wizardProps.goToStep(RunSteps.CONFIGURE_METHOD)
4657
}
58+
}
59+
60+
const onNextButtonClick: React.MouseEventHandler<unknown> = () => {
61+
dispatch(ConfigurationsDataSrv.setRules(() => {
62+
wizardProps.nextStep()
63+
}))
64+
}
65+
66+
const checkedAction = (data: SelectedRuleGroup): void => {
67+
dispatch(FilterTestCaseActions.setSelectedRuleAction(data))
68+
}
69+
70+
const stackTokens: IStackTokens = {
71+
maxHeight: '100%'
72+
}
4773

48-
const onPreviousButtonClick: React.MouseEventHandler<unknown> = () => {
49-
if (configureMethod?.selectedMethod === ConfigurationMethod_AutoDetection && detectionResult.detectionResult !== undefined) {
50-
wizardProps.previousStep()
51-
} else {
52-
wizardProps.goToStep(RunSteps.CONFIGURE_METHOD)
74+
const stackItemTokens: IStackItemTokens = {
75+
padding: '0 10px'
76+
}
77+
78+
const buildCapabilitiesDropdownOptions = () => {
79+
const testSuiteName = testSuiteInfo.selectedTestSuite?.Name ?? ''
80+
const testSuiteVersion = testSuiteInfo.selectedTestSuite?.Version ?? ''
81+
const options = capabilitiesListState.displayList
82+
.filter(c => c.TestSuiteName === testSuiteName && c.TestSuiteVersion === testSuiteVersion)
83+
.map(c => {
84+
return {
85+
key: c.Id,
86+
text: `${c.Name}`
5387
}
54-
}
88+
})
5589

56-
const onNextButtonClick: React.MouseEventHandler<unknown> = () => {
57-
dispatch(ConfigurationsDataSrv.setRules(() => {
58-
wizardProps.nextStep()
59-
}))
60-
}
90+
options.unshift({
91+
key: -1,
92+
text: '(None)'
93+
})
6194

62-
const checkedAction = (data: SelectedRuleGroup): void => {
63-
dispatch(FilterTestCaseActions.setSelectedRuleAction(data))
64-
}
95+
return options
96+
}
6597

66-
const stackTokens: IStackTokens = {
67-
maxHeight: '100%'
68-
}
98+
const onCategoriesSelected = (values: string[]): void => {
99+
dispatch(FilterTestCaseActions.selectCapabilitiesFileCategoriesAction(values))
100+
}
101+
102+
const onCategoriesExpanded = (values: string[]): void => {
103+
dispatch(FilterTestCaseActions.expandCapabilitiesFileCategoriesAction(values))
104+
}
69105

70-
const stackItemTokens: IStackItemTokens = {
71-
padding: '0 10px'
106+
const setCapabilitiesFileId = (value: any) => {
107+
dispatch(FilterTestCaseActions.selectCapabilitiesFileAction(value))
108+
109+
if (value !== -1) {
110+
dispatch(TestSuitesDataSrv.getCapabilitiesConfig(value))
72111
}
112+
}
73113

74-
return (
114+
return (
75115
<StepPanel leftNav={wizard} isLoading={filterInfo.isRulesLoading || filterInfo.isCasesLoading} errorMsg={filterInfo.errorMsg} >
76116
<Stack tokens={stackTokens} verticalFill>
77117
<Stack.Item grow style={{ overflowY: 'hidden' }}>
78118
<Stack horizontal tokens={stackTokens}>
79119
<Stack.Item grow tokens={stackItemTokens} style={{ overflowY: 'auto' }}>
80-
<RuleListPanel ruleGroups={filterInfo.ruleGroup} selected={filterInfo.selectedRules} checkedAction={checkedAction} />
120+
121+
<Dropdown
122+
key={props.key}
123+
style={{ alignSelf: 'center' }}
124+
placeholder='Kindly select a capabilities file'
125+
label='Using the following capabilities file:'
126+
options={buildCapabilitiesDropdownOptions()}
127+
selectedKey={filterInfo.selectedCapabilitiesFileId}
128+
onChange={(_, newValue, __) => { const value: any = newValue?.key; setCapabilitiesFileId(value) }} />
129+
{
130+
filterInfo.selectedCapabilitiesFileId === -1
131+
? <RuleListPanel ruleGroups={filterInfo.ruleGroup} selected={filterInfo.selectedRules} checkedAction={checkedAction} />
132+
: <CapabilitiesTreePanel groups={filterInfo.groups} onChecked={(values) => onCategoriesSelected(values)}
133+
selectedCategories={filterInfo.selectedCategories}
134+
onExpanded={(values) => onCategoriesExpanded(values)}
135+
expandedCategories={filterInfo.expandedCategoriesByFile.get(filterInfo.selectedCapabilitiesFileId)!} />
136+
}
137+
81138
</Stack.Item>
82139
<Stack.Item grow tokens={stackItemTokens} style={{ overflowY: 'auto' }}>
83140
<div>Selected Test Cases {filterInfo.listSelectedCases.length}</div>
@@ -95,5 +152,5 @@ export const FilterTestCase: React.FC<any> = (props: any) => {
95152
</Stack.Item>
96153
</Stack>
97154
</StepPanel>
98-
)
155+
)
99156
}

ProtocolTestManager/PTMService/PTMService/ClientApp/src/reducers/CapabilitiesConfigReducer.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ function getDepupedCategoryInfo(testCase: ConfigTestCase): ConfigTestCaseCategor
569569
return Array.from(map.values())
570570
}
571571

572-
function parseCapabilitiesConfigJson(input: string): CapabilitiesConfig {
572+
function parseCapabilitiesConfigJson (input: string): CapabilitiesConfig {
573573
const json = changeKeyCasing(JSON.parse(input))
574574

575575
const result: CapabilitiesConfig = json.Capabilities
@@ -617,7 +617,5 @@ function parseCapabilitiesConfigJson(input: string): CapabilitiesConfig {
617617
})
618618
})
619619

620-
console.log(result)
621-
622620
return result
623621
}

0 commit comments

Comments
 (0)