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

Search Templates feature #118

Merged
merged 26 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6819f1d
Search Templates feature
felipeelia Jan 9, 2025
9a12791
PHP Lint
felipeelia Jan 9, 2025
14acb12
Update the installation script and install svn in GH Action
felipeelia Jan 9, 2025
7213999
Initial commit of php unit tests
felipeelia Jan 9, 2025
8407ba0
Adjust style handler
felipeelia Jan 10, 2025
99da081
Unit tests
felipeelia Jan 10, 2025
42c9686
Merge branch 'develop' into feature/search-templates
felipeelia Jan 14, 2025
041d0d6
Ignore .wp-env.json in the final package
felipeelia Jan 14, 2025
f8964e7
e2e tests
felipeelia Jan 14, 2025
e7bace5
Overwrite wpCliEval as the path is always elasticpress-labs
felipeelia Jan 14, 2025
233662a
Setup permalinks structure
felipeelia Jan 14, 2025
c55fee3
Test templates limit
felipeelia Jan 14, 2025
fdb73a3
Delete all templates in the account
felipeelia Jan 14, 2025
06515f4
Unit test for test_delete_all_search_templates
felipeelia Jan 14, 2025
256f100
Fix unit tests
felipeelia Jan 14, 2025
6161fb2
php lint
felipeelia Jan 14, 2025
9f7ba84
php lint
felipeelia Jan 14, 2025
ff66165
Merge branch 'develop' into feature/search-templates
felipeelia Jan 15, 2025
880739e
Remove empty space
felipeelia Jan 15, 2025
25764dd
Remove empty lines
felipeelia Jan 15, 2025
342acce
Fix text domains
felipeelia Jan 22, 2025
6fdee9f
Run delete template CLI before running the tests
burhandodhy Jan 28, 2025
943161e
Cleanup for search template
burhandodhy Jan 28, 2025
b062248
Cleanup for search template in git action
burhandodhy Jan 28, 2025
d2877ec
Typo
burhandodhy Jan 28, 2025
2b26513
Merge branch 'develop' into feature/search-templates
felipeelia Jan 28, 2025
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
5 changes: 5 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"presets": [
"@10up/babel-preset-default"
]
}
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ jobs:
- name: Check ES response
run: curl --connect-timeout 5 --max-time 10 --retry 5 --retry-max-time 40 --retry-all-errors http://127.0.0.1:8890

- name: Install SVN ( Subversion )
run: |
sudo apt-get update
sudo apt-get install subversion

- name: Setup WP Tests
run: |
bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1
Expand Down
3 changes: 0 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
70 changes: 70 additions & 0 deletions assets/js/search-templates/apps/search-templates-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* WordPress Dependencies.
*/
import { createInterpolateElement, WPElement } from '@wordpress/element';
import { Panel, Spinner } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies.
*/
import { useSearchTemplate } from '../provider';
import TemplateRow from '../components/template-row';
import NewTemplateRow from '../components/new-template-row';
import { endpointExample, searchApiDocUrl } from '../config';

/**
* Search Templates app.
*
* @returns {WPElement} App element.
*/
export default () => {
const { isLoading, templates } = useSearchTemplate();

return (
<>
<p>
{createInterpolateElement(
__(
'Search templates are Elasticsearch queries stored in ElasticPress.io servers used by the <a>Search API</a>. Please note that all the API fields are still available for custom search templates. Your templates do not to differ in post types, offset, pagination arguments, or even filters, as for those you can still use query parameters. The templates can be used for searching in different fields or applying different scores, for instance.',
'elasticpress-labs',
),
{ a: <a href={searchApiDocUrl} /> }, // eslint-disable-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
)}
</p>
<p>
{createInterpolateElement(
sprintf(
__(
'Once you have a search template saved, you can start sending requests to your endpoint URL below. Your template needs to have <code>{{ep_placeholder}}</code> in all places where the search term needs to be used.',
'elasticpress-labs',
),
endpointExample,
),
{ code: <code /> },
)}
</p>
<p>
{createInterpolateElement(
sprintf(__('<strong>Endpoint URL:</strong> <code>%s</code>'), endpointExample),
{ strong: <strong />, code: <code /> },
)}
</p>
<Panel className="ep-search-template-panel">
{isLoading ? (
<div style={{ padding: '20px', textAlign: 'center' }}>
{__('Loading...', 'elasticpress-labs')}
<Spinner />
</div>
) : (
<>
{Object.keys(templates).map((templateName) => (
<TemplateRow key={templateName} templateName={templateName} />
))}
<NewTemplateRow />
</>
)}
</Panel>
</>
);
};
60 changes: 60 additions & 0 deletions assets/js/search-templates/components/new-template-row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* WordPress Dependencies.
*/
import { Button, Flex, PanelBody, PanelRow, TextControl } from '@wordpress/components';
import { useState, WPElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies.
*/
import { useSearchTemplateDispatch } from '../provider';
import TemplateField from './template-field';

/**
* New Template Row component.
*
* @returns {WPElement}
*/
export default () => {
const [name, setName] = useState('');
const [template, setTemplate] = useState('');

const { saveTemplate } = useSearchTemplateDispatch();

const onAddNewTemplate = () => {
saveTemplate(name, template);
setName('');
setTemplate('');
};

return (
<PanelBody title={__('Add New Template', 'elasticpress-labs')} initialOpen>
<PanelRow>
<Flex direction="column" style={{ width: '100%' }}>
<TextControl
label={__('Name', 'elasticpress-labs')}
help={__(
'Template names are not editable. Double-check your template name before saving it.',
'elasticpress-labs',
)}
value={name}
onChange={setName}
/>
<TemplateField value={template} onChange={setTemplate} />
<Flex justify="flex-start">
<Button
disabled={false}
isBusy={false}
onClick={onAddNewTemplate}
type="button"
variant="primary"
>
{__('Save Template', 'elasticpress-labs')}
</Button>
</Flex>
</Flex>
</PanelRow>
</PanelBody>
);
};
80 changes: 80 additions & 0 deletions assets/js/search-templates/components/template-field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* WordPress Dependencies.
*/
import { BaseControl, Button, Flex, Notice } from '@wordpress/components';
import { createInterpolateElement, WPElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies.
*/
import { defaultTemplate } from '../config';

/**
* Template Field component.
*
* @param {object} props Component props.
* @param {string} props.value The search template as a string.
* @param {Function} props.onChange Function to be executed when the value changes.
* @param {boolean} props.disabled If the field should be enabled or not.
* @returns {WPElement}
*/
export default ({ value, onChange, disabled }) => {
const isValueValidJson = () => {
if (!value) {
return true;
}

try {
return JSON.parse(value) && !!value;
} catch (e) {
return false;
}
};

return (
<BaseControl
help={createInterpolateElement(
__(
'Make sure your template is a valid JSON object and has <code>{{ep_placeholder}}</code>, so it can be replaced by the actual search term.',
'elasticpress-labs',
),
{ code: <code /> },
)}
>
{isValueValidJson() || (
<Notice status="error" isDismissible={false}>
{__('This does not seem to be a valid JSON object.', 'elasticpress-labs')}
</Notice>
)}
<Flex style={{ marginBottom: '10px' }}>
<BaseControl.VisualLabel>
{__('Template', 'elasticpress-labs')}
</BaseControl.VisualLabel>
{defaultTemplate && (
<Button
disabled={false}
isBusy={false}
size="small"
onClick={() => {
onChange(JSON.stringify(defaultTemplate, null, '\t'));
}}
type="button"
variant="secondary"
>
{__('Import default template', 'elasticpress-labs')}
</Button>
)}
</Flex>
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
rows={10}
disabled={disabled}
style={{
width: '100%',
}}
/>
</BaseControl>
);
};
152 changes: 152 additions & 0 deletions assets/js/search-templates/components/template-row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* WordPress Dependencies.
*/
import { Button, Flex, PanelBody, PanelRow, TextControl } from '@wordpress/components';
import { useState, WPElement } from '@wordpress/element';
import { cautionFilled, cloudDownload, cloudUpload } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies.
*/
import { useSearchTemplate, useSearchTemplateDispatch } from '../provider';
import { useSettingsScreen } from '../../settings-screen';
import TemplateField from './template-field';

/**
* Template Row component.
*
* @param {object} props Component props.
* @param {string} props.templateName The search template name.
* @returns {WPElement}
*/
export default ({ templateName }) => {
const { templates } = useSearchTemplate();
const { loadTemplate, deleteTemplate, saveTemplate } = useSearchTemplateDispatch();
const { createNotice } = useSettingsScreen();

const [template, setTemplate] = useState(
templates[templateName]
? JSON.stringify(JSON.parse(templates[templateName]), null, '\t')
: '',
);
const [isSaving, setIsSaving] = useState(false);
const [icon, setIcon] = useState(null);

const onTogglePanelBody = (opening) => {
if (!opening) {
return;
}

if (templates[templateName] === null) {
setIcon(cloudDownload);
loadTemplate(templateName)
.then((response) => {
setTemplate(JSON.stringify(response, null, '\t'));
})
.catch((error) => {
createNotice(
'error',
__('Could not load your templates.', 'elasticpress-labs'),
);
// eslint-disable-next-line no-console
console.error(__('ElasticPress Labs Error: ', 'elasticpress-labs'), error);
})
.finally(() => {
setIcon(null);
});
}
};

const onValueChange = (newValue) => {
setTemplate(newValue);

if (newValue !== JSON.stringify(JSON.parse(templates[templateName]), null, '\t')) {
setIcon(cautionFilled);
}
};

const onSaveTemplate = () => {
setIcon(cloudUpload);
setIsSaving(true);
saveTemplate(templateName, template)
.then((response) => {
setTemplate(JSON.stringify(response, null, '\t'));
setIcon(null);
createNotice('success', __('Template saved.', 'elasticpress-labs'));
})
.catch((error) => {
createNotice(
'error',
__('Could not save the template. Please try again.', 'elasticpress-labs'),
);
// eslint-disable-next-line no-console
console.error(__('ElasticPress Labs Error: ', 'elasticpress-labs'), error);
})
.finally(() => {
setIcon(null);
setIsSaving(false);
});
};

const onDeleteTemplate = () => {
deleteTemplate(template).catch((error) => {
createNotice(
'error',
__('Could not delete the template. Please try again.', 'elasticpress-labs'),
);
// eslint-disable-next-line no-console
console.error(__('ElasticPress Labs Error: ', 'elasticpress-labs'), error);
});
};

return (
<PanelBody
title={templateName}
icon={icon}
key={templateName}
initialOpen={false}
onToggle={onTogglePanelBody}
>
<PanelRow>
<Flex direction="column" style={{ width: '100%' }}>
<TextControl
label={__('Name', 'elasticpress-labs')}
help={__(
'Template names are not editable. If you need a different name, delete the template and recreate it.',
'elasticpress-labs',
)}
value={templateName}
disabled
/>
<TemplateField
value={template}
onChange={onValueChange}
disabled={isSaving || templates[template] === null}
/>
<Flex justify="flex-start">
<Button
disabled={false}
isBusy={false}
onClick={onSaveTemplate}
type="button"
variant="primary"
>
{__('Save changes', 'elasticpress-labs')}
</Button>
<Button
disabled={false}
isBusy={false}
onClick={onDeleteTemplate}
type="button"
variant="secondary"
isDestructive
>
{__('Delete template', 'elasticpress-labs')}
</Button>
</Flex>
</Flex>
</PanelRow>
</PanelBody>
);
};
7 changes: 7 additions & 0 deletions assets/js/search-templates/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Window dependencies.
*/
const { defaultTemplate, endpointExample, restApiEndpoint, searchApiDocUrl } =
window.epSearchTemplates;

export { defaultTemplate, endpointExample, restApiEndpoint, searchApiDocUrl };
Loading
Loading