diff --git a/actions/favorite/index.js b/actions/favorite/index.js new file mode 100644 index 00000000..98f418a8 --- /dev/null +++ b/actions/favorite/index.js @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +const { asGenericAction } = require('../GenericAction.js'); +const { asAuthAction } = require('../AuthAction.js'); + +const { AppError } = require('../../web-src/src/helpers/ErrorMapper.js'); + +const wretch = require('wretch'); + +async function main(params) { + const { data } = params; + try { + // data is an array of objects, + // iterate over each object and make a POST reuest to the API endpoing + // https://hook.app.workfrontfusion.com/cytq82vrakjn5p8xtaashn6gm2qc3jif + // with the data object as the body + // return the response from the API + results = [] + + console.log('saving new variations ...'); + + for (const variant of data) { + let r = await wretch(`https://hook.app.workfrontfusion.com/cytq82vrakjn5p8xtaashn6gm2qc3jif`) + .headers({ + 'Content-Type': 'application/json', + }) + .post({ + data: variant + }) + .json(); + results.push(r); + } + + return { + statusCode: 200, + body: { + message: 'Favorites updated in API', + results: results + } + }; + } catch (error) { + throw new AppError(error, 'FUSION'); + } +} + +//exports.main = asAuthAction(asGenericAction(main)); +exports.main = asGenericAction(main); \ No newline at end of file diff --git a/app.config.yaml b/app.config.yaml index 149b3af2..f1595b8c 100644 --- a/app.config.yaml +++ b/app.config.yaml @@ -37,3 +37,15 @@ application: IMS_PRODUCT_CONTEXT: $IMS_PRODUCT_CONTEXT limits: timeout: 180000 + favorite: + function: actions/favorite/index.js + web: true + runtime: nodejs:18 + inputs: + LOG_LEVEL: debug + IMS_ENDPOINT: $IMS_ENDPOINT + IMS_CLIENT_ID: $IMS_CLIENT_ID + IMS_PRODUCT_CONTEXT: $IMS_PRODUCT_CONTEXT + limits: + timeout: 180000 + diff --git a/web-src/src/components/ApplicationProvider.js b/web-src/src/components/ApplicationProvider.js index 8d3f553d..40537eec 100644 --- a/web-src/src/components/ApplicationProvider.js +++ b/web-src/src/components/ApplicationProvider.js @@ -14,6 +14,7 @@ import React, { } from 'react'; import { useSetRecoilState } from 'recoil'; import { FirefallService } from '../services/FirefallService.js'; +import { RemoteFavoritesService } from '../services/RemoteFavoritesService.js'; import actions from '../config.json'; import { useShellContext } from './ShellProvider.js'; import { loadPromptTemplates, promptTemplatesState } from '../state/PromptTemplatesState.js'; @@ -22,6 +23,7 @@ const APP_VERSION = process.env.REACT_APP_VERSION || 'unknown'; const COMPLETE_ACTION = 'complete'; const FEEDBACK_ACTION = 'feedback'; +const FAVORITE_ACTION = 'favorite'; const PROMPTS_TEMPLATES_PARAM_NAME = 'prompts'; @@ -70,6 +72,12 @@ export const ApplicationProvider = ({ children }) => { imsOrg: user.imsOrg, accessToken: user.imsToken, }), + remoteFavoritesService: new RemoteFavoritesService({ + favoritesEndpoint: actions[FAVORITE_ACTION], + imsOrg: user.imsOrg, + accessToken: user.imsToken, + websiteUrl, + }), }); loadPromptTemplates(websiteUrl, promptTemplatesPath).then((templates) => { diff --git a/web-src/src/services/RemoteFavoritesService.js b/web-src/src/services/RemoteFavoritesService.js new file mode 100644 index 00000000..09a9f454 --- /dev/null +++ b/web-src/src/services/RemoteFavoritesService.js @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { wretchRetry } from '../helpers/NetworkHelper.js'; +import { AppError } from '../helpers/ErrorMapper.js'; + +export class RemoteFavoritesService { + constructor({ + favoritesEndpoint, + imsOrg, + accessToken, + websiteUrl, + }) { + this.favoritesEndpoint = favoritesEndpoint; + this.imsOrg = imsOrg; + this.accessToken = accessToken; + this.websiteUrl = websiteUrl; + + console.debug(`Favorites Endpoint: ${this.favoritesEndpoint}`); + } + + async get_favorites() { + return wretchRetry(`${this.websiteUrl}/generated-copy.json`).get().json(); + } + + async save_favorite(data) { + try { + /* eslint-disable-next-line camelcase */ + const response = await wretchRetry(this.favoritesEndpoint) + .post({ + data, + imsOrg: this.imsOrg, + accessToken: this.accessToken, + }) + .json(); + return response; + } catch (error) { + throw new AppError(error, 'AIO'); + } + } +} diff --git a/web-src/src/state/FavoritesState.js b/web-src/src/state/FavoritesState.js index 7a1f9472..2aa26ce5 100644 --- a/web-src/src/state/FavoritesState.js +++ b/web-src/src/state/FavoritesState.js @@ -11,11 +11,15 @@ */ import { atom } from 'recoil'; import { createPersistentStorageEffect } from './PersistentStorageEffect.js'; +import { createRemotePersistenceEffect } from './PersistentRemoteStorageEffect.js'; const STORAGE_KEY = 'favorites'; export const favoritesState = atom({ key: 'favoritesState', default: [], - effects: [createPersistentStorageEffect(STORAGE_KEY)], + effects: [ + createRemotePersistenceEffect(STORAGE_KEY), + createPersistentStorageEffect(STORAGE_KEY), + ], }); diff --git a/web-src/src/state/PersistentRemoteStorageEffect.js b/web-src/src/state/PersistentRemoteStorageEffect.js new file mode 100644 index 00000000..f5324b56 --- /dev/null +++ b/web-src/src/state/PersistentRemoteStorageEffect.js @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { useApplicationContext } from '../components/ApplicationProvider.js'; + +export const createRemotePersistenceEffect = (key) => ({ setSelf, onSet }) => { + // Flag to indicate if the setSelf was called for initialization + let isInitializing = true; + const { remoteFavoritesService } = useApplicationContext(); + + // load the favorites from the API when the atom is used + remoteFavoritesService.get_favorites() + .then((favs) => { + if (favs != null) { + const favsData = favs.data; + // remove items from favsData that have the same "id" value + const uniqueFavs = favsData.filter((item, index, self) => index === self.findIndex((t) => ( + t.id === item.id + ))); + // for each item in uniqueFavs, covert "content" field from string to json + uniqueFavs.forEach((item) => { + // eslint-disable-next-line no-param-reassign + item.content = JSON.parse(item.content); + }); + setSelf(uniqueFavs); + isInitializing = false; + } + }) + .catch((error) => console.error('Error loading favorites:', error)); + + // save the favorites to the API when the atom is updated + onSet(async (newValue, _, isReset) => { + // if newValue's array length is 0, then do return + if (newValue.length === 0) { + return; + } + if (isReset) { + return; + } + + if (isInitializing) { + return; + } + + // remove items from favs that have the same "id" value + const favs = newValue; + const uniqueFavs = favs.filter((item, index, self) => index === self.findIndex((t) => ( + t.id === item.id + ))); + + console.log('Favorites to be stored:', favs); + await remoteFavoritesService.save_favorite(favs) + .then((response) => response) + .then((data) => console.log('Favorites updated in API:', data)) + .catch((error) => console.error('Error updating favorites:', error)); + }); +};