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

Io/filter by style for examples #762

Merged
merged 10 commits into from
Dec 24, 2023
39 changes: 20 additions & 19 deletions __tests__/examples.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import forEach from 'lodash/forEach';
import has from 'lodash/has';
import isEqual from 'lodash/isEqual';
import {
getExamples,
getExample,
getExamplesV2,
getExampleV2,
} from './shared/commands';
import {
MAIN_KEY,
EXAMPLE_KEYS_V1,
EXAMPLE_KEYS_V2,
INVALID_ID,
NONEXISTENT_ID,
} from './shared/constants';
import { getExamples, getExample, getExamplesV2, getExampleV2 } from './shared/commands';
import { MAIN_KEY, EXAMPLE_KEYS_V1, EXAMPLE_KEYS_V2, INVALID_ID, NONEXISTENT_ID } from './shared/constants';
import { expectUniqSetsOfResponses } from './shared/utils';
import ExampleStyleEnum from '../src/shared/constants/ExampleStyleEnum';

describe('MongoDB Examples', () => {
describe('/GET mongodb examples V1', () => {
Expand All @@ -30,6 +20,13 @@ describe('MongoDB Examples', () => {
expect(res.body.length).toBeLessThanOrEqual(10);
});

it('should return an example by searching with style', async () => {
const res = await getExamples({ style: ExampleStyleEnum.PROVERB }, { apiKey: MAIN_KEY });
expect(res.status).toEqual(200);
expect(res.body.length).toBeGreaterThanOrEqual(1);
expect(res.body[0].style).toEqual(ExampleStyleEnum.PROVERB);
});

it('should return one example', async () => {
const res = await getExamples({}, { apiKey: MAIN_KEY });
const result = await getExample(res.body[0].id);
Expand All @@ -46,7 +43,7 @@ describe('MongoDB Examples', () => {
expect(result.error).not.toEqual(undefined);
});

it('should return an error because document doesn\'t exist', async () => {
it("should return an error because document doesn't exist", async () => {
const res = await getExample(INVALID_ID);
expect(res.status).toEqual(400);
expect(res.body.error).not.toEqual(undefined);
Expand All @@ -63,11 +60,7 @@ describe('MongoDB Examples', () => {
});

it('should return different sets of example suggestions for pagination', async () => {
const res = await Promise.all([
getExamples({ page: 0 }),
getExamples({ page: 1 }),
getExamples({ page: 2 }),
]);
const res = await Promise.all([getExamples({ page: 0 }), getExamples({ page: 1 }), getExamples({ page: 2 })]);
expectUniqSetsOfResponses(res);
});

Expand Down Expand Up @@ -107,6 +100,14 @@ describe('MongoDB Examples', () => {
expect(example.igbo).not.toEqual(undefined);
});
});

it('should return examples by style', async () => {
const res = await getExamples({ style: ExampleStyleEnum.PROVERB });
expect(res.status).toEqual(200);
forEach(res.body, (example) => {
expect(example.style).toEqual(ExampleStyleEnum.PROVERB);
});
});
});

describe('/GET mongodb examples V2', () => {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dev": "npm-run-all -p start:watch start:emulators start:database",
"predev:full": "firebase functions:config:set env.redis_url=redis://localhost:6379 env.replica_set=true env.redis_status=true",
"dev:full": "npm-run-all -p start:watch start:emulators start:database:replica",
"dev:light": "npm-run-all -p start:watch start start:database",
"migrate-up": "migrate-mongo up",
"migrate-down": "migrate-mongo down",
"mongodump": "shx rm -rf dump/ && mongodump -d igbo_api -o dump",
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/Navbar/SubMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { noop } from 'lodash';
import { render } from '@testing-library/react';
import TestContext from '../components/TestContext';
import SubMenu from '../../pages/components/Navbar/SubMenu';
Expand All @@ -7,7 +8,7 @@ describe('SubMenu', () => {
it('renders the sub menu', async () => {
const { findByText } = render(
<TestContext>
<SubMenu isVisible />
<SubMenu onSelect={noop} isVisible />
</TestContext>
);

Expand Down
33 changes: 11 additions & 22 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ if (dotenv) {
}

export const isBuild = config?.runtime?.env === Environment.BUILD || process.env.NODE_ENV === Environment.BUILD;
export const isProduction = (
config?.runtime?.env === Environment.PRODUCTION || process.env.NODE_ENV === Environment.PRODUCTION
);
export const isDevelopment = (
config?.runtime?.env === Environment.DEVELOPMENT || process.env.NODE_ENV === Environment.DEVELOPMENT
);
export const isProduction =
config?.runtime?.env === Environment.PRODUCTION || process.env.NODE_ENV === Environment.PRODUCTION;
export const isDevelopment =
config?.runtime?.env === Environment.DEVELOPMENT || process.env.NODE_ENV === Environment.DEVELOPMENT;
export const isTest = config?.runtime?.env === Environment.TEST || process.env.NODE_ENV === Environment.TEST;
const useReplicaSet = config?.env?.replica_set;

Expand All @@ -31,26 +29,18 @@ const DB_NAME = 'igbo_api';
const TEST_DB_NAME = 'test_igbo_api';

// If running inside Docker container, it will fallback to using test_igbo_api database
const isTestingEnvironment = (
isTest
|| (
process.env.CONTAINER_HOST === 'mongodb'
&& !isDevelopment
&& !isProduction
)
);
const isTestingEnvironment = isTest || (process.env.CONTAINER_HOST === 'mongodb' && !isDevelopment && !isProduction);
export const PORT = 8080;
export const MONGO_HOST = process.env.CONTAINER_HOST || '127.0.0.1';
export const REPLICA_SET_NAME = 'rs0';
export const FIRST_REPLICA_SET_PORT = '2717';
export const SECOND_REPLICA_SET_PORT = '2727';
export const THIRD_REPLICA_SET_PORT = '2737';
export const FALLBACK_MONGO_PORT = '27017';
export const REPLICA_SET_MONGO_ROOT = (
`mongodb://${MONGO_HOST}:${FIRST_REPLICA_SET_PORT},`
+ `${MONGO_HOST}:${SECOND_REPLICA_SET_PORT},`
+ `${MONGO_HOST}:${THIRD_REPLICA_SET_PORT}`
);
export const REPLICA_SET_MONGO_ROOT =
`mongodb://${MONGO_HOST}:${FIRST_REPLICA_SET_PORT},` +
`${MONGO_HOST}:${SECOND_REPLICA_SET_PORT},` +
`${MONGO_HOST}:${THIRD_REPLICA_SET_PORT}`;
export const FALLBACK_MONGO_ROOT = `mongodb://${MONGO_HOST}:${FALLBACK_MONGO_PORT}`;
export const MONGO_ROOT = useReplicaSet ? REPLICA_SET_MONGO_ROOT : FALLBACK_MONGO_ROOT;
export const QUERIES = useReplicaSet ? `?replicaSet=${REPLICA_SET_NAME}` : '';
Expand All @@ -59,9 +49,8 @@ const LOCAL_MONGO_URI = `${MONGO_ROOT}/${DB_NAME}`;
export const MONGO_URI = isTestingEnvironment
? TEST_MONGO_URI.concat(QUERIES)
: isDevelopment
? LOCAL_MONGO_URI.concat(QUERIES)
: config?.env?.mongo_uri
|| LOCAL_MONGO_URI.concat(QUERIES);
? LOCAL_MONGO_URI.concat(QUERIES)
: config?.env?.mongo_uri || LOCAL_MONGO_URI.concat(QUERIES);
export const FIREBASE_CONFIG = config?.env?.firebase_config; // Provide your own Firebase Config
export const CLIENT_TEST = config?.env?.client_test;

Expand Down
5 changes: 3 additions & 2 deletions src/controllers/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,14 @@ const searchExamples = async ({ redisClient, searchWord, query, version, skip, l
/* Returns examples from MongoDB */
export const getExamples: MiddleWare = async (req, res, next) => {
try {
const { version, searchWord, regex, skip, limit, redisClient, isUsingMainKey, ...rest } = await handleQueries(req);
const { version, searchWord, regex, skip, limit, redisClient, isUsingMainKey, flags, ...rest } =
await handleQueries(req);
const regexMatch =
!isUsingMainKey && !searchWord
? {
igbo: { $exists: false },
}
: searchExamplesRegexQuery(regex);
: searchExamplesRegexQuery({ regex, flags });
const responseData = await searchExamples({
searchWord,
redisClient,
Expand Down
15 changes: 13 additions & 2 deletions src/controllers/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import WordClass from '../../shared/constants/WordClass';
import { getAllCachedVerbsAndSuffixes, setAllCachedVerbsAndSuffixes } from '../../APIs/RedisAPI';
import convertToSkipAndLimit from './convertToSkipAndLimit';
import parseRange from './parseRange';
import { WordData, Keyword } from './types';
import { WordData, Keyword, Flags } from './types';
import { Filters, ExampleWithPronunciation } from '../types';
import ExampleStyles from '../../shared/constants/ExampleStyles';

const createSimpleRegExp = (keywords: { text: string }[]) => ({
wordReg: new RegExp(
Expand Down Expand Up @@ -115,6 +116,7 @@ export const handleQueries = async ({
strict: strictQuery,
dialects: dialectsQuery,
examples: examplesQuery,
style: stylesQuery,
tags: tagsQuery,
wordClasses: wordClassesQuery,
resolve: resolveQuery,
Expand Down Expand Up @@ -232,6 +234,8 @@ export const handleQueries = async ({
const strict = strictQuery === 'true';
const dialects = dialectsQuery === 'true';
const examples = examplesQuery === 'true';
// @ts-expect-error toUpperCase
const style = stylesQuery && ExampleStyles[stylesQuery.toUpperCase()].value!;
const tags = tagsQuery
? tagsQuery
.replace(/[[\]']/g, '')
Expand All @@ -245,11 +249,18 @@ export const handleQueries = async ({
.map((wordClass) => wordClass.trim())
: [];
const resolve = resolveQuery === 'true';
const flags = {
const flags: Flags = {
dialects,
examples,
style,
resolve,
};
console.log(
`Search flags:
${Object.entries(flags)
.map(([key, value]) => `[${key}=${value}]`)
.join(',')}`
);
const filters: Filters = {
...(tags?.length ? { tags: { $in: tags } } : {}),
...(wordClasses?.length ? { 'definitions.wordClass': { $in: wordClasses } } : {}),
Expand Down
5 changes: 3 additions & 2 deletions src/controllers/utils/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cjkRange } from '../../shared/constants/diacriticCodes';
import WordClass from '../../shared/constants/WordClass';
import Tenses from '../../shared/constants/Tenses';
import StopWords from '../../shared/constants/StopWords';
import { Keyword } from './types';
import { Flags, Keyword } from './types';
import createRegExp, { SearchRegExp } from '../../shared/utils/createRegExp';
import { Filters } from '../types';

Expand Down Expand Up @@ -105,8 +105,9 @@ const definitionsQuery = ({
});

/* Regex match query used to later to defined the Content-Range response header */
export const searchExamplesRegexQuery = (regex: SearchRegExp) => ({
export const searchExamplesRegexQuery = ({ regex, flags }: { regex: SearchRegExp; flags: Flags }) => ({
$or: [{ igbo: regex.wordReg }, { english: regex?.definitionsReg }],
...(flags.style ? { style: flags.style } : {}),
});
export const searchIgboTextSearch = fullTextSearchQuery;
export const searchDefinitionsWithinIgboTextSearch = fullTextDefinitionsSearchQuery;
Expand Down
7 changes: 7 additions & 0 deletions src/controllers/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ export type Keyword = {
definitionsReg?: RegExp;
};
};

export type Flags = {
dialects: boolean;
examples: boolean;
style: string;
resolve: boolean;
};
14 changes: 7 additions & 7 deletions src/controllers/words.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { createExample } from './examples';
import { wordSchema } from '../models/Word';
import { handleWordFlags } from '../APIs/FlagsAPI';
import minimizeWords from './utils/minimizeWords';
import { MiddleWare, LegacyWord } from '../types';
import { MiddleWare, LegacyWord, WordDocument } from '../types';
import { WordResponseData } from './types';

const isEnglish = isWord('american-english');
Expand Down Expand Up @@ -131,8 +131,8 @@ export const getWord: MiddleWare = async (req, res, next) => {
};

/* Creates Word documents in MongoDB database for testing */
export const createWord = async (data: Partial<LegacyWord>, connection: mongoose.Connection) => {
const Word = connection.model('Word', wordSchema);
export const createWord = async (data: Partial<LegacyWord>, connection: mongoose.Connection): Promise<WordDocument> => {
const Word = connection.model<WordDocument>('Word', wordSchema);
const { examples, word, wordClass, definitions, variations, stems, dialects, ...rest } = data;

const wordData = {
Expand All @@ -146,13 +146,13 @@ export const createWord = async (data: Partial<LegacyWord>, connection: mongoose
};

const newWord = new Word(wordData);
await newWord.save();
const savedWord = await newWord.save();

/* Go through each word's example and create an Example document */
const savedExamples = map(examples, async (example) => {
const exampleData = {
...example,
associatedWords: [newWord.id],
associatedWords: [savedWord.id],
};
const createdExample = await createExample(exampleData, connection);
return createdExample;
Expand All @@ -161,6 +161,6 @@ export const createWord = async (data: Partial<LegacyWord>, connection: mongoose
/* Wait for all the Examples to be created and then add them to the Word document */
const resolvedExamples = await Promise.all(savedExamples);
const exampleIds = getDocumentsIds(resolvedExamples);
newWord.examples = exampleIds;
return newWord.save();
savedWord.examples = exampleIds;
return savedWord.save();
};
Loading
Loading