Skip to content

Commit 947a4ca

Browse files
authored
feat(clients): add replaceAllObjectsWithTransformation (#5013)
1 parent 9dd4d5f commit 947a4ca

17 files changed

+527
-85
lines changed

playground/php/src/search.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@
8989
// }
9090
// var_dump($rules);
9191

92-
$configForIngestion = $config->setFullHosts(['http://localhost:6689'])->setTransformationRegion('eu');
92+
$configForIngestion = $config->setTransformationRegion('eu');
9393

9494
$clientWithTransformation = SearchClient::createWithConfig($configForIngestion);
9595

96-
var_dump($clientWithTransformation->saveObjectsWithTransformation('boyd', [['objectID' => '1', 'name' => 'Michel']], true));
96+
var_dump($clientWithTransformation->replaceAllObjectsWithTransformation('boyd', [['objectID' => '1', 'name' => 'Michel']], true));

playground/python/app/ingestion.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
from os import environ
12
from asyncio import run
23

34
from algoliasearch.ingestion import __version__
45
from algoliasearch.ingestion.client import IngestionClient
6+
from dotenv import load_dotenv
57

8+
load_dotenv("../.env")
69

710
async def main():
811
print("IngestionClient version", __version__)
912

10-
client = IngestionClient("FOO", "BAR")
13+
client = IngestionClient(environ.get("ALGOLIA_APPLICATION_ID"), environ.get("ALGOLIA_ADMIN_KEY"), "eu")
1114

1215
print("client initialized", client)
1316

playground/python/app/search.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,26 @@
1111
def main():
1212
print("SearchClient version", __version__)
1313

14-
client = SearchClientSync(
15-
environ.get("ALGOLIA_APPLICATION_ID"), environ.get("ALGOLIA_ADMIN_KEY")
16-
)
17-
client.add_user_agent("playground")
18-
client.add_user_agent("bar", "baz")
19-
20-
print("user_agent", client._config._user_agent.get())
21-
print("client initialized", client)
22-
23-
try:
24-
resp = client.search_synonyms("foo")
25-
print(resp)
26-
client.browse_synonyms("foo", lambda _resp: print(_resp))
27-
finally:
28-
client.close()
29-
30-
print("client closed")
31-
32-
print("with transformations")
33-
14+
# client = SearchClientSync(
15+
# environ.get("ALGOLIA_APPLICATION_ID"), environ.get("ALGOLIA_ADMIN_KEY")
16+
# )
17+
# client.add_user_agent("playground")
18+
# client.add_user_agent("bar", "baz")
19+
#
20+
# print("user_agent", client._config._user_agent.get())
21+
# print("client initialized", client)
22+
#
23+
# try:
24+
# resp = client.search_synonyms("foo")
25+
# print(resp)
26+
# client.browse_synonyms("foo", lambda _resp: print(_resp))
27+
# finally:
28+
# client.close()
29+
#
30+
# print("client closed")
31+
#
32+
# print("with transformations")
33+
#
3434
config = SearchConfig(
3535
environ.get("ALGOLIA_APPLICATION_ID"), environ.get("ALGOLIA_ADMIN_KEY")
3636
)
@@ -46,10 +46,12 @@ def main():
4646
print("user_agent", client._config._user_agent.get())
4747

4848
try:
49-
resp = client.save_objects_with_transformation(
50-
"foo", [{"objectID": "bar"}], wait_for_tasks=True
49+
resp = client.replace_all_objects_with_transformation(
50+
"boyd", [{"objectID": "bar"},{"objectID": "bar"},{"objectID": "bar"},{"objectID": "bar"},{"objectID": "bar"}], 2
5151
)
5252
print(resp)
53+
except Exception as e:
54+
print(e)
5355
finally:
5456
client.close()
5557

playground/python/poetry.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/cts/runCts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ export async function runCts(
155155
assertValidTimeouts(languages.length);
156156
assertChunkWrapperValid(languages.length - skip('dart'));
157157
assertValidReplaceAllObjects(languages.length - skip('dart'));
158-
assertValidReplaceAllObjectsWithTransformation(only('javascript'));
158+
assertValidReplaceAllObjectsWithTransformation(
159+
only('javascript') + only('go') + only('python') + only('java') + only('php'),
160+
);
159161
assertValidAccountCopyIndex(only('javascript'));
160162
assertValidReplaceAllObjectsFailed(languages.length - skip('dart'));
161163
assertValidReplaceAllObjectsScopes(languages.length - skip('dart'));

scripts/cts/testServer/replaceAllObjectsWithTransformation.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function addRoutes(app: Express): void {
9292
)?.[1] as string;
9393
expect(raowtState).to.include.keys(lang);
9494
expect(req.body.action === 'addObject').to.equal(true);
95+
expect(req.query.referenceIndexName === `cts_e2e_replace_all_objects_with_transformation_${lang}`).to.equal(true);
9596

9697
raowtState[lang].pushCount += req.body.records.length;
9798

@@ -104,7 +105,14 @@ function addRoutes(app: Express): void {
104105
});
105106

106107
app.get('/1/runs/:runID/events/:eventID', (req, res) => {
107-
res.json({ status: 'finished' });
108+
res.json({
109+
status: 'succeeded',
110+
eventID: '113b2068-6337-4c85-b5c2-e7b213d82921',
111+
runID: 'b1b7a982-524c-40d2-bb7f-48aab075abda',
112+
type: 'fetch',
113+
batchSize: 1,
114+
publishedAt: '2022-05-12T06:24:30.049Z',
115+
});
108116
});
109117

110118
app.get('/1/indexes/:indexName/task/:taskID', (req, res) => {

specs/search/helpers/replaceAllObjectsWithTransformation.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ method:
55
- Records
66
x-available-languages:
77
- javascript
8+
- go
9+
- java
10+
- php
11+
- python
812
operationId: replaceAllObjectsWithTransformation
913
summary: Replace all records in an index
1014
description: |

templates/go/api.mustache

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,16 +227,23 @@ func toRequestOptions[T RequestOption](opts []T) []RequestOption {
227227
return requestOpts
228228
}
229229

230-
func toIngestionRequestOptions(opts []RequestOption) []ingestion.RequestOption {
231-
requestOpts := make([]ingestion.RequestOption, 0, len(opts))
230+
// toIngestionChunkedBatchOptions converts the current chunked batch opts to ingestion ones.
231+
func toIngestionChunkedBatchOptions(opts []ChunkedBatchOption) []ingestion.ChunkedBatchOption {
232+
conf := config{}
232233

233234
for _, opt := range opts {
234-
if opt, ok := opt.(ingestion.RequestOption); ok {
235-
requestOpts = append(requestOpts, opt)
236-
}
235+
opt.apply(&conf)
237236
}
238237

239-
return requestOpts
238+
ingestionOpts := make([]ingestion.ChunkedBatchOption, 0, len(opts))
239+
240+
if conf.batchSize > 0 {
241+
ingestionOpts = append(ingestionOpts, ingestion.WithBatchSize(conf.batchSize))
242+
}
243+
244+
ingestionOpts = append(ingestionOpts, ingestion.WithWaitForTasks(conf.waitForTasks))
245+
246+
return ingestionOpts
240247
}
241248

242249
func toIterableOptions(opts []ChunkedBatchOption) []IterableOption {

templates/go/ingestion_helpers.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ChunkedPush Chunks the given `objects` list in subset of 1000 elements max in or
99
@return []WatchResponse - List of push responses.
1010
@return error - Error if any.
1111
*/
12-
func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, action Action, referenceIndexName *string, opts ...RequestOption) ([]WatchResponse, error) {
12+
func (c *APIClient) ChunkedPush(indexName string, objects []map[string]any, action Action, referenceIndexName *string, opts ...ChunkedBatchOption) ([]WatchResponse, error) {
1313
conf := config{
1414
headerParams: map[string]string{},
1515
waitForTasks: false,

templates/go/search_helpers.mustache

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,100 @@ func (c *APIClient) ChunkedBatch(indexName string, objects []map[string]any, act
599599
return responses, nil
600600
}
601601

602+
/*
603+
ReplaceAllObjectsWithTransformation is similar to the `replaceAllObjects` method but requires a Push connector (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/) to be created first, in order to transform records before indexing them to Algolia. The `region` must have been passed to the client instantiation method.
604+
See https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation details.
605+
606+
@param indexName string - the index name to replace objects into.
607+
@param objects []map[string]any - List of objects to replace.
608+
@param opts ...ReplaceAllObjectsOption - Optional parameters for the request.
609+
@return *ReplaceAllObjectsResponse - The response of the replace all objects operation.
610+
@return error - Error if any.
611+
*/
612+
func (c *APIClient) ReplaceAllObjectsWithTransformation(indexName string, objects []map[string]any, opts ...ReplaceAllObjectsOption) (*ReplaceAllObjectsWithTransformationResponse, error) {
613+
if c.ingestionTransporter == nil {
614+
return nil, reportError("`region` must be provided at client instantiation before calling this method.")
615+
}
616+
617+
tmpIndexName := fmt.Sprintf("%s_tmp_%d", indexName, time.Now().UnixNano())
618+
619+
conf := config{
620+
headerParams: map[string]string{},
621+
scopes: []ScopeType{SCOPE_TYPE_SETTINGS, SCOPE_TYPE_RULES, SCOPE_TYPE_SYNONYMS},
622+
}
623+
624+
for _, opt := range opts {
625+
opt.apply(&conf)
626+
}
627+
628+
opts = append(opts, WithWaitForTasks(true))
629+
630+
copyResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope(conf.scopes))), toRequestOptions(opts)...)
631+
if err != nil {
632+
return nil, err
633+
}
634+
635+
watchResp, err := c.ingestionTransporter.ChunkedPush(tmpIndexName, objects, ingestion.Action(ACTION_ADD_OBJECT), &indexName, toIngestionChunkedBatchOptions(replaceAllObjectsToChunkBactchOptions(opts))...)
636+
if err != nil {
637+
_, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName))
638+
639+
return nil, err //nolint:wrapcheck
640+
}
641+
642+
_, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...)
643+
if err != nil {
644+
_, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName))
645+
646+
return nil, err
647+
}
648+
649+
copyResp, err = c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope(conf.scopes))), toRequestOptions(opts)...)
650+
if err != nil {
651+
_, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName))
652+
653+
return nil, err
654+
}
655+
656+
_, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, replaceAllObjectsToIterableOptions(opts)...)
657+
if err != nil {
658+
_, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName))
659+
660+
return nil, err
661+
}
662+
663+
moveResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(tmpIndexName, NewOperationIndexParams(OPERATION_TYPE_MOVE, indexName)), toRequestOptions(opts)...)
664+
if err != nil {
665+
_, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName))
666+
667+
return nil, err
668+
}
669+
670+
_, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, replaceAllObjectsToIterableOptions(opts)...)
671+
if err != nil {
672+
_, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName))
673+
674+
return nil, err
675+
}
676+
677+
var searchWatchResp []WatchResponse
678+
679+
rawResp, err := json.Marshal(watchResp)
680+
if err != nil {
681+
return nil, fmt.Errorf("unable to convert the ingestion WatchResponse to search WatchResponse: %w", err)
682+
}
683+
684+
err = json.Unmarshal(rawResp, &searchWatchResp)
685+
if err != nil {
686+
return nil, fmt.Errorf("unable to convert the ingestion WatchResponse to search WatchResponse: %w", err)
687+
}
688+
689+
return &ReplaceAllObjectsWithTransformationResponse{
690+
CopyOperationResponse: *copyResp,
691+
WatchResponses: searchWatchResp,
692+
MoveOperationResponse: *moveResp,
693+
}, nil
694+
}
695+
602696
/*
603697
ReplaceAllObjects replaces all objects (records) in the given `indexName` with the given `objects`. A temporary index is created during this process in order to backup your data.
604698
See https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation details.
@@ -708,7 +802,7 @@ func (c *APIClient) SaveObjectsWithTransformation(indexName string, objects []ma
708802
return nil, reportError("`region` must be provided at client instantiation before calling this method.")
709803
}
710804

711-
return c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(ACTION_ADD_OBJECT), nil, toIngestionRequestOptions(toRequestOptions(opts))...) //nolint:wrapcheck
805+
return c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(ACTION_ADD_OBJECT), nil, toIngestionChunkedBatchOptions(opts)...) //nolint:wrapcheck
712806
}
713807

714808
/*
@@ -725,22 +819,22 @@ func (c *APIClient) PartialUpdateObjectsWithTransformation(indexName string, obj
725819
return nil, reportError("`region` must be provided at client instantiation before calling this method.")
726820
}
727821

728-
conf := config{
729-
headerParams: map[string]string{},
730-
createIfNotExists: true,
731-
}
822+
conf := config{
823+
headerParams: map[string]string{},
824+
createIfNotExists: true,
825+
}
732826

733-
for _, opt := range opts {
734-
opt.apply(&conf)
735-
}
827+
for _, opt := range opts {
828+
opt.apply(&conf)
829+
}
736830

737-
var action Action
831+
var action Action
738832

739-
if conf.createIfNotExists {
740-
action = ACTION_PARTIAL_UPDATE_OBJECT
741-
} else {
742-
action = ACTION_PARTIAL_UPDATE_OBJECT_NO_CREATE
743-
}
833+
if conf.createIfNotExists {
834+
action = ACTION_PARTIAL_UPDATE_OBJECT
835+
} else {
836+
action = ACTION_PARTIAL_UPDATE_OBJECT_NO_CREATE
837+
}
744838

745-
return c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(action), nil, toIngestionRequestOptions(toRequestOptions(opts))...) //nolint:wrapcheck
839+
return c.ingestionTransporter.ChunkedPush(indexName, objects, ingestion.Action(action), nil, toIngestionChunkedBatchOptions(partialUpdateObjectsToChunkedBatchOptions(opts))...) //nolint:wrapcheck
746840
}

0 commit comments

Comments
 (0)