diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml new file mode 100644 index 00000000..a28f46a6 --- /dev/null +++ b/.github/workflows/deploy-docker.yml @@ -0,0 +1,56 @@ +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + push: + branches: ['feat/EMP-3684-create-docker-image'] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - uses: actions/setup-node@v4 + with: + node-version: '18' + - name: Installing dependencies + run: npm ci + shell: bash + - name: Build project + run: npm run build:docker + shell: bash + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.prettierignore b/.prettierignore index 67165d87..e69de29b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +0,0 @@ -main.ts \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..50f4a4bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM nginx:alpine3.18-slim + +RUN mkdir -p /opt/empathy +WORKDIR /opt/empathy +ADD dist dist + +COPY dist /usr/share/nginx/html \ No newline at end of file diff --git a/build/instrumentation.build.mjs b/build/instrumentation.build.mjs index b6a5bb21..ee3021b8 100644 --- a/build/instrumentation.build.mjs +++ b/build/instrumentation.build.mjs @@ -74,6 +74,9 @@ export function createConfig({ replace( mergeConfig('replace', { 'process.env.NODE_ENV': JSON.stringify('production'), + 'process.env.VUE_APP_DEVELOPMENT_DOCKER': process.env.VUE_APP_DEVELOPMENT_DOCKER + ? JSON.stringify(true) + : JSON.stringify(false), STRIP_SSR_INJECTOR: true, preventAssignment: true }) diff --git a/package.json b/package.json index 5ee24656..9d18a398 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "private": true, "scripts": { "serve": "vue-cli-service serve --skip-plugins eslint", + "serve:docker": "VUE_APP_DEVELOPMENT_DOCKER=true vue-cli-service serve --skip-plugins eslint", "build": "rollup -c", + "build:docker": "VUE_APP_DEVELOPMENT_DOCKER=true rollup -c", "build:serve": "concurrently \"rollup -c -w\" \"npm run serve:dist\"", "serve:dist": "http-server dist -a localhost --cors", "cy:open": "cypress open", diff --git a/src/adapter/docker.adapter.ts b/src/adapter/docker.adapter.ts new file mode 100644 index 00000000..37e66d07 --- /dev/null +++ b/src/adapter/docker.adapter.ts @@ -0,0 +1,77 @@ +import { + searchEndpointAdapter, + PlatformAdapter, + nextQueriesEndpointAdapter, + identifierResultsEndpointAdapter, + popularSearchesEndpointAdapter, + recommendationsEndpointAdapter, + querySuggestionsEndpointAdapter, + relatedTagsEndpointAdapter, + semanticQueriesEndpointAdapter, + experienceControlsEndpointAdapter +} from '@empathyco/x-adapter-platform'; +import { XComponentsAdapter } from '@empathyco/x-types'; + +/** + * Mutates adapter to point to environment context. + * + * @param adapter - PlatformAdapter the adapter to mutate. + */ +export function overrideAdapter(adapter: PlatformAdapter): void { + adapter.search = searchEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('search', extraParams!) + }); + adapter.popularSearches = popularSearchesEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('popularSearches', extraParams!) + }); + adapter.recommendations = recommendationsEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('recommendations', extraParams!) + }); + adapter.nextQueries = nextQueriesEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('nextQueries', extraParams!) + }); + adapter.querySuggestions = querySuggestionsEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('querySuggestions', extraParams!) + }); + adapter.relatedTags = relatedTagsEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('relatedTags', extraParams!) + }); + adapter.identifierResults = identifierResultsEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('identifierResults', extraParams!) + }); + adapter.semanticQueries = semanticQueriesEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('semanticQueries', extraParams!) + }); + adapter.experienceControls = experienceControlsEndpointAdapter.extends({ + endpoint: ({ extraParams }) => resolveEmpathyEndpoint('experienceControls', extraParams!) + }); +} + +type DockerEndpoints = Exclude; + +/** + * Function that returns the url for each empathy's endpoints according to the environment context. + * + * @param endpoint - The endpoint to resolve the url. + * @param context - The environment context where to retrieve the information + * needed to resolve the endpoint. + * + * @returns The url for the given endpoint. + */ +function resolveEmpathyEndpoint(endpoint: DockerEndpoints, context: Record): string { + const { empathyAPIHost, instance } = context; + const endpointHost: string = empathyAPIHost ? empathyAPIHost : 'localhost:8080'; + const endpointInstance: string = instance ? instance : 'imdb'; + const empathyEndpoints: Record = { + search: `http://${endpointHost}/query/${endpointInstance}/search`, + popularSearches: `http://${endpointHost}/query/${endpointInstance}/empathize`, + recommendations: `http://${endpointHost}/query/${endpointInstance}/topclicked`, + nextQueries: `http://${endpointHost}/nextqueries/${endpointInstance}`, + querySuggestions: `http://${endpointHost}/query/${endpointInstance}/empathize`, + relatedTags: `http://${endpointHost}/relatedtags/${endpointInstance}`, + identifierResults: `http://${endpointHost}/query/${endpointInstance}/skusearch`, + semanticQueries: `http://${endpointHost}/semantics-api/search_single/${endpointInstance}`, + experienceControls: `http://${endpointHost}/config/v1/public/configs` + }; + return empathyEndpoints[endpoint]; +} diff --git a/src/main.ts b/src/main.ts index 8feb1157..e48e2245 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ -import { CssInjector } from "@empathyco/x-archetype-utils"; +import { CssInjector } from '@empathyco/x-archetype-utils'; import { XInstaller } from '@empathyco/x-components'; import Vue from 'vue'; -import { installXOptions } from './x-components/plugin.options'; +import { getInstallXOptions } from './x-components/plugin.options'; declare global { interface Window { @@ -13,4 +13,4 @@ Vue.config.productionTip = false; Vue.config.devtools = window.__enableVueDevtools__ ?? false; new CssInjector(true); -new XInstaller(installXOptions).init(); +getInstallXOptions().then(installXOptions => new XInstaller(installXOptions).init()); diff --git a/src/x-components/plugin.options.ts b/src/x-components/plugin.options.ts index d4b2522f..5be6f9e7 100644 --- a/src/x-components/plugin.options.ts +++ b/src/x-components/plugin.options.ts @@ -9,45 +9,57 @@ import { mergeSemanticQueriesConfigWire } from './wiring/semantic-queries.wiring const device = useDevice(); -export const installXOptions: InstallXOptions = { - adapter, - store, - app: App, - domElement: getDomElement, - xModules: { - facets: { - config: { - filtersStrategyForRequest: 'leaves-only' - } - }, - semanticQueries: { - config: { - threshold: 50, - maxItemsToRequest: 10 +/** + * Function that returns the options to install x-components. + * + * Returns - the InstallXOptions. + */ +export async function getInstallXOptions(): Promise { + if (process.env.VUE_APP_DEVELOPMENT_DOCKER) { + const { overrideAdapter } = await import('../adapter/docker.adapter'); + overrideAdapter(adapter); + (window.initX as SnippetConfig).lang = 'es'; + } + return { + adapter, + store, + app: App, + domElement: getDomElement, + xModules: { + facets: { + config: { + filtersStrategyForRequest: 'leaves-only' + } }, - wiring: { - SemanticQueriesConfigProvided: { - mergeSemanticQueriesConfigWire + semanticQueries: { + config: { + threshold: 50, + maxItemsToRequest: 10 + }, + wiring: { + SemanticQueriesConfigProvided: { + mergeSemanticQueriesConfigWire + } } } - } - }, - async installExtraPlugins({ vue, snippet }) { - const i18n = await I18n.create({ - locale: snippet.uiLang, - device: (snippet.device as string) ?? device.deviceName.value, - fallbackLocale: 'en', - messages - }); - vue.use(i18n); - vue.prototype.$setLocale = i18n.setLocale.bind(i18n); - vue.prototype.$setLocaleDevice = i18n.setDevice.bind(i18n); + }, + async installExtraPlugins({ vue, snippet }) { + const i18n = await I18n.create({ + locale: snippet.uiLang, + device: (snippet.device as string) ?? device.deviceName.value, + fallbackLocale: 'en', + messages + }); + vue.use(i18n); + vue.prototype.$setLocale = i18n.setLocale.bind(i18n); + vue.prototype.$setLocaleDevice = i18n.setDevice.bind(i18n); - return { - i18n: i18n.vueI18n - }; - } -}; + return { + i18n: i18n.vueI18n + }; + } + }; +} /** * Creates a DOM element to mount the X Components app.