Skip to content
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
1 change: 1 addition & 0 deletions lib/Constants/FilterOperator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ enum FilterOperator: string {
case IS_LESS_THAN = 'is-lower-than';
case IS_LESS_THAN_OR_EQUAL = 'is-lower-than-or-equal';
case IS_EMPTY = 'is-empty';
case IS_NOT_EMPTY = 'is-not-empty';
}
22 changes: 22 additions & 0 deletions lib/Db/Row2Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,31 @@ private function getFilterExpression(IQueryBuilder $qb, Column $column, string $
$qb->expr()->isNull('value'),
$qb->expr()->eq('value', $qb->createNamedParameter('', $paramType))
);
} elseif ($column->getType() === 'selection' && $column->getSubtype() === 'multi') {
$filterExpression = $qb2->expr()->orX(
$qb->expr()->isNull('value'),
$qb->expr()->eq('value', $qb->createNamedParameter('[]', $paramType))
);
} else {
$filterExpression = $qb->expr()->isNull('value');
Comment thread
enjeck marked this conversation as resolved.
}
break;
case 'is-not-empty':
Comment thread
enjeck marked this conversation as resolved.
$includeDefault = !empty($defaultValue);
if ($column->getType() === Column::TYPE_TEXT) {
$filterExpression = $qb2->expr()->andX(
$qb->expr()->isNotNull('value'),
$qb->expr()->neq('value', $qb->createNamedParameter('', $paramType))
);
} elseif ($column->getType() === 'selection' && $column->getSubtype() === 'multi') {
$filterExpression = $qb2->expr()->andX(
$qb->expr()->isNotNull('value'),
$qb->expr()->neq('value', $qb->createNamedParameter('[]', $paramType))
);
} else {
$filterExpression = $qb->expr()->isNotNull('value');
Comment thread
enjeck marked this conversation as resolved.
}
break;
default:
throw new InternalError('Operator ' . $operator . ' is not supported.');
}
Expand Down Expand Up @@ -636,6 +657,7 @@ private function getSqlOperator(string $operator, IQueryBuilder $qb, string $col
'is-lower-than' => $qb->expr()->lt($columnName, $qb->createNamedParameter($value, $paramType)),
'is-lower-than-or-equal' => $qb->expr()->lte($columnName, $qb->createNamedParameter($value, $paramType)),
'is-empty' => $qb->expr()->isNull($columnName),
'is-not-empty' => $qb->expr()->isNotNull($columnName),
default => throw new InternalError('Operator ' . $operator . ' is not supported.'),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default class DatetimeColumn extends AbstractDatetimeColumn {
[FilterIds.IsLowerThan]() { return filterDate.isAfter(valueDate) },
[FilterIds.IsLowerThanOrEqual]() { return filterDate.isSameOrAfter(valueDate) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default class DatetimeDateColumn extends AbstractDatetimeColumn {
[FilterIds.IsLowerThan]() { return filterDate.isAfter(valueDate) },
[FilterIds.IsLowerThanOrEqual]() { return filterDate.isSameOrAfter(valueDate) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default class DatetimeTimeColumn extends AbstractDatetimeColumn {
[FilterIds.IsLowerThan]() { return filterTime.isAfter(valueTime) },
[FilterIds.IsLowerThanOrEqual]() { return filterTime.isSameOrAfter(valueTime) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default class NumberColumn extends AbstractNumberColumn {
[FilterIds.IsLowerThan]() { return parseInt(cell.value) < parseInt(filterValue) },
[FilterIds.IsLowerThanOrEqual]() { return parseInt(cell.value) <= parseInt(filterValue) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default class NumberProgressColumn extends AbstractNumberColumn {
[FilterIds.IsLowerThan]() { return parseInt(cell.value) < parseInt(filterValue) },
[FilterIds.IsLowerThanOrEqual]() { return parseInt(cell.value) <= parseInt(filterValue) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Comment thread
Koc marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default class NumberStarsColumn extends AbstractNumberColumn {
[FilterIds.IsLowerThan]() { return parseInt(cell.value ? cell.value : 0) < parseInt(filterValue) },
[FilterIds.IsLowerThanOrEqual]() { return parseInt(cell.value ? cell.value : 0) <= parseInt(filterValue) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default class RelationColumn extends AbstractColumn {
[FilterIds.IsEqual]() { return cellLabel === filterValue },
[FilterIds.IsNotEqual]() { return cellLabel !== filterValue },
[FilterIds.IsEmpty]() { return !cellLabel },
[FilterIds.IsNotEmpty]() { return !!cellLabel },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default class SelectionColumn extends AbstractSelectionColumn {
[FilterIds.IsEqual]() { return cellLabel === filterValue.toLowerCase() },
[FilterIds.IsNotEqual]() { return cellLabel !== filterValue.toLowerCase() },
[FilterIds.IsEmpty]() { return !cellLabel },
[FilterIds.IsNotEmpty]() { return !!cellLabel },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default class SelectionMutliColumn extends AbstractSelectionColumn {
[FilterIds.IsEqual]() { return valueString === filterValue.toLowerCase() },
[FilterIds.IsNotEqual]() { return valueString !== filterValue.toLowerCase() },
[FilterIds.IsEmpty]() { return !valueString },
[FilterIds.IsNotEmpty]() { return !!valueString },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class TextLineColumn extends AbstractTextColumn {
isFilterFound(cell, filter) {
const filterValue = (filter.magicValuesEnriched ? filter.magicValuesEnriched : filter.value).toLowerCase()
const cellValue = cell.value?.toLowerCase()
if (!cellValue & filter.operator.id !== FilterIds.IsEmpty) return false
if (!cellValue && filter.operator.id !== FilterIds.IsEmpty) return false
const filterMethod = {
[FilterIds.Contains]() { return cellValue.includes(filterValue) },
[FilterIds.DoesNotContain]() { return !cellValue.includes(filterValue) },
Expand All @@ -53,6 +53,7 @@ export default class TextLineColumn extends AbstractTextColumn {
[FilterIds.IsEqual]() { return cellValue === filterValue },
[FilterIds.IsNotEqual]() { return cellValue !== filterValue },
[FilterIds.IsEmpty]() { return !cellValue },
[FilterIds.IsNotEmpty]() { return !!cellValue },
}[filter.operator.id]

return super.isFilterFound(filterMethod, cell)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default class TextLinkColumn extends AbstractTextColumn {
[FilterIds.IsEqual]() { return value === filterValue },
[FilterIds.IsNotEqual]() { return value !== filterValue },
[FilterIds.IsEmpty]() { return !value },
[FilterIds.IsNotEmpty]() { return !!value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class TextLongColumn extends AbstractTextColumn {
[FilterIds.Contains]() { return cellValue && cellValue.includes(filterValue) },
[FilterIds.DoesNotContain]() { return cellValue && !cellValue.includes(filterValue) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default class TextRichColumn extends AbstractTextColumn {
[FilterIds.Contains]() { return cellValue && cellValue.includes(filterValue) },
[FilterIds.DoesNotContain]() { return cellValue && !cellValue.includes(filterValue) },
[FilterIds.IsEmpty]() { return !cell.value },
[FilterIds.IsNotEmpty]() { return !!cell.value },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default class UsergroupColumn extends AbstractUsergroupColumn {
[FilterIds.IsEqual]() { return valueString === filterValue },
[FilterIds.IsNotEqual]() { return valueString !== filterValue },
[FilterIds.IsEmpty]() { return !valueString },
[FilterIds.IsNotEmpty]() { return !!valueString },
}[filter.operator.id]
return super.isFilterFound(filterMethod, cell)
}
Expand Down
34 changes: 21 additions & 13 deletions src/shared/components/ncTable/mixins/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,86 +50,94 @@ export const FilterIds = {
IsLowerThan: 'is-lower-than',
IsLowerThanOrEqual: 'is-lower-than-or-equal',
IsEmpty: 'is-empty',
IsNotEmpty: 'is-not-empty',
}

export const Filters = {
ContainsItem: new Filter({
id: FilterIds.ContainsItem,
label: t('tables', 'Contains items'),
goodFor: [ColumnTypes.SelectionMulti, ColumnTypes.Selection],
incompatibleWith: [FilterIds.DoesNotContain, FilterIds.IsEmpty, FilterIds.IsEqual],
incompatibleWith: [FilterIds.DoesNotContain, FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual],
}),
Contains: new Filter({
id: FilterIds.Contains,
label: t('tables', 'Contains'),
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextLong, ColumnTypes.TextLink, ColumnTypes.TextRich, ColumnTypes.SelectionMulti, ColumnTypes.Usergroup, ColumnTypes.Selection, ColumnTypes.Relation],
incompatibleWith: [FilterIds.DoesNotContain, FilterIds.IsEmpty, FilterIds.IsEqual],
incompatibleWith: [FilterIds.DoesNotContain, FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual],
}),
DoesNotContain: new Filter({
id: FilterIds.DoesNotContain,
label: t('tables', 'Does not contain'),
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextLong, ColumnTypes.TextLink, ColumnTypes.TextRich, ColumnTypes.SelectionMulti, ColumnTypes.Usergroup, ColumnTypes.Selection, ColumnTypes.Relation],
incompatibleWith: [FilterIds.Contains, FilterIds.IsEmpty, FilterIds.IsEqual],
incompatibleWith: [FilterIds.Contains, FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual],
}),
BeginsWith: new Filter({
id: FilterIds.BeginsWith,
label: t('tables', 'Begins with'),
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextLink],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.BeginsWith],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.BeginsWith],
}),
EndsWith: new Filter({
id: FilterIds.EndsWith,
label: t('tables', 'Ends with'),
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextLink],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.EndsWith],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.EndsWith],
}),
IsEqual: new Filter({
id: FilterIds.IsEqual,
label: t('tables', 'Is equal'),
shortLabel: '=',
goodFor: [ColumnTypes.TextLine, ColumnTypes.Number, ColumnTypes.SelectionCheck, ColumnTypes.TextLink, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.Selection, ColumnTypes.SelectionMulti, ColumnTypes.Usergroup, ColumnTypes.Relation],
incompatibleWith: [FilterIds.IsNotEqual, FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.Contains, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
incompatibleWith: [FilterIds.IsNotEqual, FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.Contains, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
}),
IsNotEqual: new Filter({
id: FilterIds.IsNotEqual,
label: t('tables', 'Is not equal'),
shortLabel: '!=',
goodFor: [ColumnTypes.TextLine, ColumnTypes.Number, ColumnTypes.SelectionCheck, ColumnTypes.TextLink, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.Selection, ColumnTypes.SelectionMulti, ColumnTypes.Usergroup, ColumnTypes.Relation],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.Contains, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.Contains, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
}),
IsGreaterThan: new Filter({
id: FilterIds.IsGreaterThan,
label: t('tables', 'Is greater than'),
shortLabel: '>',
goodFor: [ColumnTypes.Number, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual],
}),
IsGreaterThanOrEqual: new Filter({
id: FilterIds.IsGreaterThanOrEqual,
label: t('tables', 'Is greater than or equal'),
shortLabel: '>=',
goodFor: [ColumnTypes.Number, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual],
}),
IsLowerThan: new Filter({
id: FilterIds.IsLowerThan,
label: t('tables', 'Is lower than'),
shortLabel: '<',
goodFor: [ColumnTypes.Number, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
}),
IsLowerThanOrEqual: new Filter({
id: FilterIds.IsLowerThanOrEqual,
label: t('tables', 'Is lower than or equal'),
shortLabel: '<=',
goodFor: [ColumnTypes.Number, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsNotEmpty, FilterIds.IsEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual],
}),
IsEmpty: new Filter({
id: FilterIds.IsEmpty,
label: t('tables', 'Is empty'),
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextRich, ColumnTypes.Number, ColumnTypes.TextLink, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.SelectionCheck, ColumnTypes.Usergroup, ColumnTypes.Relation],
incompatibleWith: [FilterIds.Contains, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual, FilterIds.IsEmpty],
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextRich, ColumnTypes.Number, ColumnTypes.TextLink, ColumnTypes.NumberProgress, ColumnTypes.NumberStars, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.Selection, ColumnTypes.SelectionMulti, ColumnTypes.SelectionCheck, ColumnTypes.TextLong, ColumnTypes.Usergroup, ColumnTypes.Relation],
incompatibleWith: [FilterIds.Contains, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual, FilterIds.IsEmpty, FilterIds.IsNotEmpty],
noSearchValue: true,
}),
IsNotEmpty: new Filter({
id: FilterIds.IsNotEmpty,
label: t('tables', 'Is not empty'),
goodFor: [ColumnTypes.TextLine, ColumnTypes.TextRich, ColumnTypes.Number, ColumnTypes.TextLink, ColumnTypes.NumberProgress, ColumnTypes.NumberStars, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.Selection, ColumnTypes.SelectionMulti, ColumnTypes.TextLong, ColumnTypes.Usergroup, ColumnTypes.Relation],
incompatibleWith: [FilterIds.Contains, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.IsEqual, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual, FilterIds.IsEmpty, FilterIds.IsNotEmpty],
Comment thread
Koc marked this conversation as resolved.
noSearchValue: true,
}),
}
Expand Down
2 changes: 1 addition & 1 deletion src/shared/components/ncTable/partials/FilterLabel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default {
return value
},
labelText() {
if (this.operator.id === FilterIds.IsEmpty) {
if (this.operator.noSearchValue) {
return this.operator.getOperatorLabel()
} else if (this.operator.id === FilterIds.ContainsItem) {
return this.operator.getOperatorLabel() + ' "' + this.getValue.map(item => item.label).join(', ') + '"'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export default {
changeFilterOperator(op) {
this.selectedOperator = op
this.selectOperator = false
if (op.id === FilterIds.IsEmpty) {
if (op.noSearchValue) {
this.createFilter()
} else {
this.selectValue = true
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/Db/Row2MapperFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ public static function filterDataProvider(): array {
['Charlie'],
'Filter skills equal to exactly Python (id 3)'
],
'skills is-empty' => [
[['columnId' => 'skills', 'operator' => 'is-empty', 'value' => '']],
['Diana'],
'Filter empty skills'
],
'skills is-not-empty' => [
[['columnId' => 'skills', 'operator' => 'is-not-empty', 'value' => '']],
['Alice', 'Bob', 'Charlie', 'Eve'],
'Filter non-empty skills'
],

// Selection checkbox column (is_available)
'available is checked' => [
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/Db/Row2MapperTestDependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private function initializeTestData(): void {
'department' => 'Finance',
'score' => 88.7,
'status' => 'Pending',
'skills' => ['Excel', 'Accounting', 'Analysis'],
'skills' => [],
'is_available' => '"false"',
'experience_years' => 3
]
Expand Down
Loading