Skip to content

Commit

Permalink
feat: filter project by status, type, with and or toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
steveoh committed Nov 26, 2024
1 parent 69fc2c9 commit ca65e59
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 45 deletions.
71 changes: 64 additions & 7 deletions src/components/definitionExpressionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,82 @@ describe('createDefinitionExpression', () => {
poly: '',
});
});
it('should request all records when all type filters are selected', () => {
// using 'all' and all keys
const state: State = {
projects: new Set(['Proposed']),
features: new Set(featureTypes.map(({ featureType }) => featureType)),
join: or,
};
const result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids: "Status in('Proposed')",
point: "StatusDescription in('Proposed')",
line: "StatusDescription in('Proposed')",
poly: "StatusDescription in('Proposed')",
});
});
it('should use project status values when selecting project status', () => {
const state: State = {
projects: new Set<Key>(['Proposed', 'Current']),
features: all,
join: or,
};
const result = generateDefinitionExpression(state);
let result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids: "Status in('Proposed','Current')",
point: "StatusDescription in('Proposed','Current')",
line: "StatusDescription in('Proposed','Current')",
poly: "StatusDescription in('Proposed','Current')",
});

state.join = and;

result = generateDefinitionExpression(state);
expect(result).toEqual({
centroids: "Status in('Proposed','Current')",
point: "StatusDescription in('Proposed','Current')",
line: "StatusDescription in('Proposed','Current')",
poly: "StatusDescription in('Proposed','Current')",
});
});
it('should use feature type codes when selecting feature types', () => {
const state: State = {
projects: all,
features: new Set<Key>(['Terrestrial Treatment Area']),
join: or,
};
const result = generateDefinitionExpression(state);
let result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids: 'Project_ID in(select Project_ID from POLY where TypeCode in(1))',
point: '1=0',
line: '1=0',
poly: 'TypeCode in(1)',
});

state.features = new Set<Key>(['Terrestrial Treatment Area', 'Fish passage structure', 'Dam']);
result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids:
'Project_ID in(select Project_ID from POINT where TypeCode in(9) union select Project_ID from LINE where TypeCode in(12) union select Project_ID from POLY where TypeCode in(1))',
point: 'TypeCode in(9)',
line: 'TypeCode in(12)',
poly: 'TypeCode in(1)',
});

state.features = new Set<Key>(['Dam']);
result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids: 'Project_ID in(select Project_ID from LINE where TypeCode in(12))',
point: '1=0',
line: 'TypeCode in(12)',
poly: '1=0',
});
});
it('should only apply selected feature types to its containing table', () => {
const state: State = {
Expand Down Expand Up @@ -154,17 +201,27 @@ describe('createDefinitionExpression', () => {
it('should use the user input join value intersect when selecting feature types with and', () => {
const state: State = {
projects: all,
features: new Set<Key>(['Fence', 'Dam']),
features: new Set<Key>(['Fish passage structure', 'Dam']),
join: and,
};
const result = generateDefinitionExpression(state);
let result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids:
"((Project_ID in(select Project_ID from POINT where TypeDescription='Dam' intersect Project_ID in(select Project_ID from POINT where TypeDescription='Fence')))",
'Project_ID in(select Project_ID from POINT where TypeCode=9 intersect select Project_ID from LINE where TypeCode=12)',
point:
"(Project_ID in(select Project_ID from POINT where TypeDescription='Dam' intersect Project_ID in(select Project_ID from POINT where TypeDescription='Fence')))",
line: '1=0',
'TypeCode in(9) and Project_ID in(select Project_ID from POINT where TypeCode=9 intersect select Project_ID from LINE where TypeCode=12)',
line: 'TypeCode in(12) and Project_ID in(select Project_ID from POINT where TypeCode=9 intersect select Project_ID from LINE where TypeCode=12)',
poly: '1=0',
});

state.features = new Set(['Dam']);
result = generateDefinitionExpression(state);

expect(result).toEqual({
centroids: 'Project_ID in(select Project_ID from LINE where TypeCode=12)',
point: '1=0',
line: 'TypeCode in(12) and Project_ID in(select Project_ID from LINE where TypeCode=12)',
poly: '1=0',
});
});
Expand Down
139 changes: 101 additions & 38 deletions src/components/definitionExpressionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import { featureTypes, projectStatus } from './data/filters';
const allRecords = '';
const noRecords = '1=0' as const;
const all = 'all' as const;
const and = 'and' as const;
const or = 'or' as const;
const addPossibleConjunction = (phrase: string) => {
if (!phrase || [allRecords, noRecords].includes(phrase)) {
return phrase;
}

return `${phrase} and `;
};

const full = featureTypes.reduce(
(acc, { type }) => {
Expand Down Expand Up @@ -67,22 +73,22 @@ const generateExpressions = (
projectPredicate: string,
featurePredicates: Record<
'point' | 'line' | 'poly',
{
code: number;
type: string;
}[]
| {
code: number;
type: string;
}[]
| string
>,
join: 'and' | 'or',
) => {
// if the join style is or, we can use sql in statements for each table
if (join === or) {
const result = {
centroids: '',
point: '',
line: '',
poly: '',
};
const result = {
centroids: '',
point: '',
line: '',
poly: '',
};

if (join === or) {
// if there is a project status filter, we can use it for all tables
if (projectPredicate) {
const featureStatusExpression = `StatusDescription in(${projectPredicate})`;
Expand All @@ -95,13 +101,9 @@ const generateExpressions = (

const expressions = [];

if ((featurePredicates?.point ?? 0).length) {
if (featurePredicates.point.length === full.point) {
result.point = allRecords;
} else {
if (result.point) {
result.point += ' and ';
}
if (Array.isArray(featurePredicates.point)) {
if (featurePredicates.point.length !== full.point) {
result.point = addPossibleConjunction(result.point);

const codes = featurePredicates.point.map(({ code }) => code).join(',');

Expand All @@ -112,13 +114,9 @@ const generateExpressions = (
result.point = typeof featurePredicates?.point === 'string' ? result.point : '1=0';
}

if ((featurePredicates?.line ?? 0).length) {
if (featurePredicates.line.length === full.line) {
result.point = allRecords;
} else {
if (result.line) {
result.line += ' and ';
}
if (Array.isArray(featurePredicates.line)) {
if (featurePredicates.line.length !== full.line) {
result.line = addPossibleConjunction(result.line);

const codes = featurePredicates.line.map(({ code }) => code).join(',');

Expand All @@ -129,13 +127,9 @@ const generateExpressions = (
result.line = typeof featurePredicates?.line === 'string' ? result.line : '1=0';
}

if ((featurePredicates?.poly ?? 0).length) {
if (featurePredicates.poly.length === full.poly) {
result.point = allRecords;
} else {
if (result.poly) {
result.poly += ' and ';
}
if (Array.isArray(featurePredicates.poly)) {
if (featurePredicates.poly.length !== full.poly) {
result.poly = addPossibleConjunction(result.poly);

const codes = featurePredicates.poly.map(({ code }) => code).join(',');

Expand All @@ -152,21 +146,91 @@ const generateExpressions = (

if (result.centroids) {
if (expressions.length > 0) {
result.centroids += ' and ';
result.centroids = addPossibleConjunction(result.centroids);
}

if (expressions.length === 1) {
result.centroids += `Project_ID in(${expressions[0]})`;

return result;
}
}

result.centroids += `Project_ID in(${expressions.join(` union `)})`;

return result;
} else {
if (projectPredicate) {
const featureStatusExpression = `StatusDescription in(${projectPredicate})`;

result.centroids = `Status in(${projectPredicate})`;
result.point = featureStatusExpression;
result.line = featureStatusExpression;
result.poly = featureStatusExpression;
}

const expressions = [];

result.centroids += `Project_ID in(${expressions.join(` union `)})`;
if (Array.isArray(featurePredicates.point)) {
result.point = addPossibleConjunction(result.point);

const predicate = featurePredicates.point
.map(({ code }) => `select Project_ID from POINT where TypeCode=${code}`)
.join(' intersect ');

expressions.push(predicate);
result.point += `TypeCode in${featurePredicates.point.map(({ code }) => `(${code})`).join(',')}`;
} else {
result.point = typeof featurePredicates?.point === 'string' ? result.point : '1=0';
}

if (Array.isArray(featurePredicates.line)) {
result.line = addPossibleConjunction(result.line);

const predicate = featurePredicates.line
.map(({ code }) => `select Project_ID from LINE where TypeCode=${code}`)
.join(' intersect ');

expressions.push(predicate);
result.line += `TypeCode in${featurePredicates.line.map(({ code }) => `(${code})`).join(',')}`;
} else {
result.line = typeof featurePredicates?.line === 'string' ? result.line : '1=0';
}

if (Array.isArray(featurePredicates.poly)) {
result.poly = addPossibleConjunction(result.poly);

const predicate = featurePredicates.poly
.map(({ code }) => `select Project_ID from POLY where TypeCode=${code}`)
.join(' intersect ');

expressions.push(predicate);
result.poly += `TypeCode in${featurePredicates.poly.map(({ code }) => `(${code})`).join(',')}`;
} else {
result.poly = typeof featurePredicates?.poly === 'string' ? result.poly : '1=0';
}

if (expressions.length === 0) {
return result;
}

result.centroids += `Project_ID in(${expressions.join(` union `)})`;
result.centroids = addPossibleConjunction(result.centroids);
result.point = addPossibleConjunction(result.point);
result.line = addPossibleConjunction(result.line);
result.poly = addPossibleConjunction(result.poly);

const expression = `Project_ID in(${expressions.join(` intersect `)})`;
result.centroids += expression;

if (result.point && result.point != noRecords) {
result.point += expression;
}
if (result.line && result.line != noRecords) {
result.line += expression;
}
if (result.poly && result.poly != noRecords) {
result.poly += expression;
}

return result;
}
Expand Down Expand Up @@ -194,7 +258,6 @@ export const generateDefinitionExpression = ({
};
}

// empty set mean no filters so no records can be matched
if (
projectPredicate === allRecords &&
Object.entries(featurePredicates).every(([key, value]) => value.length === full[key as keyof typeof full])
Expand Down

0 comments on commit ca65e59

Please sign in to comment.