Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add not missing filter for experiments table #4308

Merged
merged 1 commit into from
Jul 19, 2023
Merged
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
271 changes: 271 additions & 0 deletions extension/src/experiments/model/filterBy/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
import { FilterDefinition, filterExperiment, Operator } from '.'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[F] This file got purged in #3585. I brought it back/modified it to bring back some coverage.

import { buildMetricOrParamPath } from '../../columns/paths'
import { Experiment, ColumnType } from '../../webview/contract'

describe('filterExperiment', () => {
const paramsFile = 'params.yaml'
const experiments = [
{
Created: '2020-12-29T12:00:01',
id: 1,
params: {
'params.yaml': {
bool: true,
filter: 1,
sort: 1,
text: 'abcdefghijklmnop'
}
}
},
{
Created: '2020-12-30T12:00:01',
id: 2,
params: {
'params.yaml': {
bool: false,
filter: 2,
sort: 1,
text: 'fun'
}
}
},
{
Created: '2021-01-01T00:00:01',
id: 3,
params: {
'params.yaml': {
bool: null,
filter: 3,
sort: 1,
text: 'not missing'
}
}
}
] as unknown as Experiment[]

const filterExperiments = (filters: FilterDefinition[]): Experiment[] =>
experiments
.map(experiment => filterExperiment(filters, experiment))
.filter(Boolean) as Experiment[]

it('should not filter experiments if they do not have the provided value (for queued experiments)', () => {
const unfiltered = filterExperiments([
{
operator: Operator.IS_FALSE,
path: buildMetricOrParamPath(ColumnType.METRICS, 'metrics.json', 'acc'),
value: undefined
}
])

expect(
unfiltered
.map(
experiment => experiment[ColumnType.METRICS]?.['metrics.json']?.acc
)
.filter(Boolean)
).toHaveLength(0)

expect(unfiltered).toStrictEqual(experiments)
})

it('should filter experiments if they do not have the provided value and not missing is used', () => {
const unfiltered = filterExperiments([
{
operator: Operator.NOT_MISSING,
path: buildMetricOrParamPath(ColumnType.METRICS, 'metrics.json', 'acc'),
value: undefined
}
])

expect(unfiltered).toStrictEqual([])
})

it('should not filter the experiments if no filters are provided', () => {
const unfiltered = filterExperiments([])

expect(unfiltered).toStrictEqual(experiments)
})

it('should filter the experiments with a greater than filter', () => {
const unfiltered = filterExperiments([
{
operator: Operator.GREATER_THAN,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '2'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([3])
})

it('should filter experiments by an equals filter', () => {
const unfiltered = filterExperiments([
{
operator: Operator.EQUAL,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '2'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([2])
})

it('should filter experiments by a not equals filter', () => {
const unfiltered = filterExperiments([
{
operator: Operator.NOT_EQUAL,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '2'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([1, 3])
})

it('should filter experiments by multiple filters', () => {
const unfiltered = filterExperiments([
{
operator: Operator.GREATER_THAN,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '0'
},
{
operator: Operator.LESS_THAN_OR_EQUAL,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '2'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([1, 2])
})

it('should filter experiments by multiple filters on multiple params', () => {
const unfiltered = filterExperiments([
{
operator: Operator.GREATER_THAN_OR_EQUAL,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '0'
},
{
operator: Operator.LESS_THAN,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '10'
},
{
operator: Operator.EQUAL,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'sort'),
value: '10'
}
])

expect(unfiltered).toStrictEqual([])
})

it('should filter experiments using string contains', () => {
const unfiltered = filterExperiments([
{
operator: Operator.CONTAINS,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'text'),
value: 'def'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([1])
})

it('should filter experiments if given a numeric column to filter with string contains', () => {
const unfiltered = filterExperiments([
{
operator: Operator.CONTAINS,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '1'
}
])

expect(unfiltered).toStrictEqual([])
})

it('should filter experiments when given a numeric column to filter with string does not contain', () => {
const unfiltered = filterExperiments([
{
operator: Operator.NOT_CONTAINS,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'filter'),
value: '1'
}
])

expect(unfiltered).toStrictEqual(experiments)
})

it('should filter experiments using string does not contain', () => {
const unfiltered = filterExperiments([
{
operator: Operator.NOT_CONTAINS,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'text'),
value: 'def'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([2, 3])
})

it('should split the experiments using boolean is true', () => {
const unfiltered = filterExperiments([
{
operator: Operator.IS_TRUE,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'bool'),
value: undefined
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([1])
})

it('should split the experiments using boolean is false', () => {
const unfiltered = filterExperiments([
{
operator: Operator.IS_FALSE,
path: buildMetricOrParamPath(ColumnType.PARAMS, paramsFile, 'bool'),
value: undefined
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([2])
})

it('should split the experiments using after Created date', () => {
const unfiltered = filterExperiments([
{
operator: Operator.AFTER_DATE,
path: 'Created',
value: '2020-12-31T15:40:00'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([3])
})

it('should split the experiments using before Created date', () => {
const unfiltered = filterExperiments([
{
operator: Operator.BEFORE_DATE,
path: 'Created',
value: '2020-12-31T15:40:00'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([1, 2])
})

it('should split the experiments using on Created date', () => {
const unfiltered = filterExperiments([
{
operator: Operator.ON_DATE,
path: 'Created',
value: '2020-12-31T15:40:00'
}
])

expect(unfiltered.map(experiment => experiment.id)).toStrictEqual([])
})
})
12 changes: 8 additions & 4 deletions extension/src/experiments/model/filterBy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { definedAndNonEmpty } from '../../../util/array'
import { splitColumnPath } from '../../columns/paths'

export enum Operator {
EQUAL = '==',
EQUAL = '=',
GREATER_THAN = '>',
GREATER_THAN_OR_EQUAL = '>=',
LESS_THAN = '<',
LESS_THAN_OR_EQUAL = '<=',
NOT_EQUAL = '!=',
NOT_EQUAL = '≠',

NOT_MISSING = '≠Ø',

CONTAINS = '∈',
NOT_CONTAINS = '!∈',
NOT_CONTAINS = '',

IS_TRUE = '⊤',
IS_FALSE = '⊥',
Expand Down Expand Up @@ -56,9 +58,11 @@ const evaluate = <T extends string | number | boolean>(
filterValue: T
): boolean => {
if (valueToEvaluate === undefined) {
return true
return operator !== Operator.NOT_MISSING
}
switch (operator) {
case Operator.NOT_MISSING:
return true
case Operator.GREATER_THAN:
return valueToEvaluate > filterValue
case Operator.LESS_THAN:
Expand Down
12 changes: 11 additions & 1 deletion extension/src/experiments/model/filterBy/quickPick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export const OPERATORS = [
types: ['number', 'string'],
value: Operator.NOT_EQUAL
},
{
description: 'Not Missing',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would an antonym of missing make more sense? Like "Defined" or "Contains"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Missing"/"Missing values" is a Data Science term that most users will be familiar with.

label: Operator.NOT_MISSING,
types: ['string', 'boolean', 'number'],
value: Operator.NOT_MISSING
},
{
description: 'Is true',
label: Operator.IS_TRUE,
Expand Down Expand Up @@ -130,7 +136,11 @@ export const pickFilterToAdd = async (
return
}

if ([Operator.IS_TRUE, Operator.IS_FALSE].includes(operator)) {
if (
[Operator.IS_TRUE, Operator.IS_FALSE, Operator.NOT_MISSING].includes(
operator
)
) {
return {
operator,
path: picked.path,
Expand Down
10 changes: 5 additions & 5 deletions extension/src/experiments/model/filterBy/tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ describe('ExperimentsFilterByTree', () => {
const filters = await experimentsFilterByTree.getChildren()
expect(filters).toStrictEqual([
{
description: '== 90000',
description: '= 90000',
dvcRoot: 'demo',
id: buildMetricOrParamPath(
ColumnType.PARAMS,
'params.yaml',
'param==90000'
'param=90000'
),
label: buildMetricOrParamPath(ColumnType.PARAMS, 'params.yaml', 'param')
}
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('ExperimentsFilterByTree', () => {
it("should return the dvcRoot's filters if one is provided", async () => {
const mockedFilters = [
{
operator: '==',
operator: '=',
path: buildMetricOrParamPath(ColumnType.PARAMS, 'params.yml', 'param'),
value: 90000
},
Expand All @@ -146,12 +146,12 @@ describe('ExperimentsFilterByTree', () => {

expect(filters).toStrictEqual([
{
description: '== 90000',
description: '= 90000',
dvcRoot: 'demo',
id: buildMetricOrParamPath(
ColumnType.PARAMS,
'params.yml',
'param==90000'
'param=90000'
),
label: buildMetricOrParamPath(ColumnType.PARAMS, 'params.yml', 'param')
},
Expand Down
2 changes: 1 addition & 1 deletion extension/src/test/suite/experiments/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1723,7 +1723,7 @@ suite('Experiments Test Suite', () => {
const firstFilterId = buildMetricOrParamPath(
ColumnType.PARAMS,
'params.yaml',
'test==1'
'test=1'
)
const firstFilterDefinition = {
operator: Operator.EQUAL,
Expand Down
Loading