Update Dockerfile (#2523)

Co-authored-by: Francisco Marques <franciscopcmarques@gmail.com>
This commit is contained in:
Krzysztof Żuraw 2022-11-16 16:01:34 +01:00 committed by GitHub
parent 1eecdfa5c1
commit ac063c6410
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 222 additions and 76 deletions

View file

@ -0,0 +1,74 @@
name: Publish container image
on:
push:
tags:
# Matches stable and pre-releases
- "[0-9]+.[0-9]+.[0-9]+*"
jobs:
docker:
runs-on: ubuntu-20.04
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
# Outputs the name of the repository (owner/repo)
- name: Get image name
id: image
run: |
IMAGE_NAME=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')
echo ::set-output name=image_name::ghcr.io/${IMAGE_NAME}
# Tags stable versions as :latest
# Pre-releases, alphas, etc. as :snapshot
- name: Output image tags from git tag events
if: ${{ startsWith(github.ref, 'refs/tags/') }}
run: |
# Remove everything else than the tagged version
DASHBOARD_VERSION=${GITHUB_REF#refs/tags/}
echo "
DASHBOARD_VERSION=${DASHBOARD_VERSION}
CONTAINER_TAGS=${{ steps.image.outputs.image_name }}:${DASHBOARD_VERSION}
" >> "${GITHUB_ENV}"
- name: Set up Docker QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
context: ./
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.CONTAINER_TAGS }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
build-args: |
COMMIT_ID=${{ github.sha }}
PROJECT_VERSION=$DASHBOARD_VERSION
- name: Output image digest
run: |
echo $"\
Digest: ${{ steps.docker_build.outputs.digest }}
Tags: ${{ env.CONTAINER_TAGS }}"

View file

@ -1,19 +1,46 @@
FROM node:18 as builder FROM node:18-alpine as builder
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm ci --legacy-peer-deps
COPY . .
ARG APP_MOUNT_URI COPY nginx/ nginx/
COPY assets/ assets/
COPY locale/ locale/
COPY testUtils testUtils/
COPY codegen.yml .
COPY webpack.config.js .
COPY tsconfig.json .
COPY *.d.ts .
COPY schema.graphql .
COPY introspection.json .
COPY src/ src/
ARG API_URI ARG API_URI
ARG APP_MOUNT_URI
ARG MARKETPLACE_URL ARG MARKETPLACE_URL
ARG SALEOR_APPS_ENDPOINT ARG SALEOR_APPS_ENDPOINT
ARG STATIC_URL ARG STATIC_URL
ENV API_URI ${API_URI:-http://localhost:8000/graphql/} ENV API_URI ${API_URI:-http://localhost:8000/graphql/}
ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/dashboard/} ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/dashboard/}
ENV MARKETPLACE_URL ${MARKETPLACE_URL}
ENV SALEOR_APPS_ENDPOINT=${SALEOR_APPS_ENDPOINT}
ENV STATIC_URL ${STATIC_URL:-/dashboard/} ENV STATIC_URL ${STATIC_URL:-/dashboard/}
RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} MARKETPLACE_URL=${MARKETPLACE_URL} SALEOR_APPS_ENDPOINT=${SALEOR_APPS_ENDPOINT} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build
FROM nginx:stable RUN npm run build
FROM nginx:stable-alpine as runner
WORKDIR /app WORKDIR /app
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY ./nginx/replace-api-url.sh /docker-entrypoint.d/50-replace-api-url.sh
COPY --from=builder /app/build/ /app/ COPY --from=builder /app/build/ /app/
LABEL org.opencontainers.image.title="saleor/saleor-dashboard" \
org.opencontainers.image.description="A GraphQL-powered, single-page dashboard application for Saleor." \
org.opencontainers.image.url="https://saleor.io/" \
org.opencontainers.image.source="https://github.com/saleor/saleor-dashboard" \
org.opencontainers.image.revision="$COMMIT_ID" \
org.opencontainers.image.version="$PROJECT_VERSION" \
org.opencontainers.image.authors="Saleor Commerce (https://saleor.io)" \
org.opencontainers.image.licenses="BSD 3"

View file

@ -1,16 +0,0 @@
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG APP_MOUNT_URI
ARG API_URI
ARG MARKETPLACE_URL
ARG SALEOR_APPS_ENDPOINT
ARG STATIC_URL
ENV API_URI ${API_URI:-http://localhost:8000/graphql/}
ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/}
ENV STATIC_URL ${STATIC_URL:-/}
EXPOSE 9000
CMD npm start -- --host 0.0.0.0

View file

@ -145,6 +145,22 @@ You are ready to run cypress commands like:
npm run cy:open npm run cy:open
``` ```
### Usage with docker
Build docker image:
```shell
docker build --tag saleor-dashboard .
```
Run nginx from docker and bind it to port on your machine (in this example 8080):
```shell
docker run --publish 8080:80 --env "API_URL=<YOUR_API_URL>" saleor-dashboard
```
Enter `http://localhost:8080/` to use dashboard.
##### Usage with Sentry adapter: ##### Usage with Sentry adapter:
Sentry is used as the default tracker so no changes in code are necessary and the configuration is done via environment variables. Sentry is used as the default tracker so no changes in code are necessary and the configuration is done via environment variables.

View file

@ -29,7 +29,7 @@ export const urlList = {
variants: "variant/", variants: "variant/",
vouchers: "discounts/vouchers/", vouchers: "discounts/vouchers/",
variant: "variant/", variant: "variant/",
warehouses: "warehouses/" warehouses: "warehouses/",
}; };
export const addVariantUrl = productId => export const addVariantUrl = productId =>

View file

@ -38,7 +38,7 @@ module.exports = async (on, config) => {
config.env.mailHogUrl = process.env.CYPRESS_mailHogUrl; config.env.mailHogUrl = process.env.CYPRESS_mailHogUrl;
config.env.grepTags = process.env.CYPRESS_grepTags; config.env.grepTags = process.env.CYPRESS_grepTags;
on("before:browser:launch", ({}, launchOptions) => { on("before:browser:launch", (_browser = {}, launchOptions) => {
launchOptions.args.push("--proxy-bypass-list=<-loopback>"); launchOptions.args.push("--proxy-bypass-list=<-loopback>");
return launchOptions; return launchOptions;
}); });

View file

@ -1,19 +1,19 @@
// / <reference types="cypress" /> // / <reference types="cypress" />
import "./customCommands/user"; import "cypress-file-upload";
import "cypress-mailhog";
import "cypress-mochawesome-reporter/register";
import "./customCommands/basicOperations"; import "./customCommands/basicOperations";
import "./customCommands/deleteElementsViaApi"; import "./customCommands/deleteElementsViaApi";
import "./customCommands/softAssertions";
import "./customCommands/sharedElementsOperations/addressForm.js"; import "./customCommands/sharedElementsOperations/addressForm.js";
import "./customCommands/sharedElementsOperations/assignElementsForm.js"; import "./customCommands/sharedElementsOperations/assignElementsForm.js";
import "./customCommands/sharedElementsOperations/confirmationMessages.js"; import "./customCommands/sharedElementsOperations/confirmationMessages.js";
import "./customCommands/sharedElementsOperations/deleteElement";
import "./customCommands/sharedElementsOperations/progressBar.js"; import "./customCommands/sharedElementsOperations/progressBar.js";
import "./customCommands/sharedElementsOperations/selects.js"; import "./customCommands/sharedElementsOperations/selects.js";
import "./customCommands/sharedElementsOperations/tables"; import "./customCommands/sharedElementsOperations/tables";
import "./customCommands/sharedElementsOperations/deleteElement"; import "./customCommands/softAssertions";
import "cypress-mailhog"; import "./customCommands/user";
import "cypress-file-upload";
import "cypress-mochawesome-reporter/register";
import { commandTimings } from "cypress-timings"; import { commandTimings } from "cypress-timings";
@ -49,7 +49,7 @@ Cypress.Commands.add("addAliasToGraphRequest", operationName => {
Cypress.on( Cypress.on(
"uncaught:exception", "uncaught:exception",
(err, runnable) => (_err, _runnable) =>
// returning false here prevents Cypress from // returning false here prevents Cypress from
// failing the test // failing the test
false, false,

16
nginx/replace-api-url.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env sh
# Replaces the API_URL from the bundle's index.html file with the $API_URL env var.
# This script is automatically picked up by the nginx entrypoint on startup.
set -e
INDEX_BUNDLE_PATH="/app/dashboard/index.html"
if [ -z "${API_URL}" ]; then
echo "No API_URL provided, using defaults."
else
echo "Setting API_URL to: $API_URL"
sed -i "s#API_URL:.*#API_URL: \"$API_URL\",#" "$INDEX_BUNDLE_PATH"
fi

View file

@ -6,7 +6,7 @@ import {
RedirectAction, RedirectAction,
} from "@saleor/app-sdk/app-bridge"; } from "@saleor/app-sdk/app-bridge";
import { appPath } from "@saleor/apps/urls"; import { appPath } from "@saleor/apps/urls";
import { APP_MOUNT_URI } from "@saleor/config"; import { getAppMountUri } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import React from "react"; import React from "react";
@ -78,7 +78,7 @@ export const useAppActions = (
if (newContext) { if (newContext) {
window.open(to); window.open(to);
} else if (appDeepUrlChange) { } else if (appDeepUrlChange) {
const exactLocation = urlJoin(APP_MOUNT_URI, to); const exactLocation = urlJoin(getAppMountUri(), to);
// Change only url without reloading if we are in the same app // Change only url without reloading if we are in the same app
window.history.pushState(null, "", exactLocation); window.history.pushState(null, "", exactLocation);

View file

@ -10,14 +10,14 @@ import { FormattedMessage } from "react-intl";
import { useStyles } from "./styles"; import { useStyles } from "./styles";
export interface CustomAppDefaultTokenProps { export interface CustomAppDefaultTokenProps {
apiUri: string; apiUrl: string;
token: string; token: string;
onApiUriClick: () => void; onApiUrlClick: () => void;
onTokenClose: () => void; onTokenClose: () => void;
} }
const CustomAppDefaultToken: React.FC<CustomAppDefaultTokenProps> = props => { const CustomAppDefaultToken: React.FC<CustomAppDefaultTokenProps> = props => {
const { apiUri, token, onApiUriClick, onTokenClose } = props; const { apiUrl, token, onApiUrlClick, onTokenClose } = props;
const classes = useStyles(props); const classes = useStyles(props);
const [copied, copy] = useClipboard(); const [copied, copy] = useClipboard();
@ -38,8 +38,8 @@ const CustomAppDefaultToken: React.FC<CustomAppDefaultTokenProps> = props => {
defaultMessage="This token gives you access to your shop's API, which you'll find here: {url}" defaultMessage="This token gives you access to your shop's API, which you'll find here: {url}"
values={{ values={{
url: ( url: (
<Link href={apiUri} onClick={onApiUriClick}> <Link href={apiUrl} onClick={onApiUrlClick}>
{apiUri} {apiUrl}
</Link> </Link>
), ),
}} }}

View file

@ -36,14 +36,14 @@ export interface CustomAppDetailsPageFormData {
permissions: PermissionEnum[]; permissions: PermissionEnum[];
} }
export interface CustomAppDetailsPageProps { export interface CustomAppDetailsPageProps {
apiUri: string; apiUrl: string;
disabled: boolean; disabled: boolean;
errors: AppErrorFragment[]; errors: AppErrorFragment[];
permissions: ShopInfoQuery["shop"]["permissions"]; permissions: ShopInfoQuery["shop"]["permissions"];
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
app: AppUpdateMutation["appUpdate"]["app"]; app: AppUpdateMutation["appUpdate"]["app"];
token: string; token: string;
onApiUriClick: () => void; onApiUrlClick: () => void;
onTokenDelete: (id: string) => void; onTokenDelete: (id: string) => void;
onTokenClose: () => void; onTokenClose: () => void;
onTokenCreate: () => void; onTokenCreate: () => void;
@ -58,14 +58,14 @@ export interface CustomAppDetailsPageProps {
const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => { const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => {
const { const {
apiUri, apiUrl,
disabled, disabled,
errors, errors,
permissions, permissions,
saveButtonBarState, saveButtonBarState,
app, app,
token, token,
onApiUriClick, onApiUrlClick,
onTokenClose, onTokenClose,
onTokenCreate, onTokenCreate,
onTokenDelete, onTokenDelete,
@ -136,9 +136,9 @@ const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => {
{token && ( {token && (
<> <>
<CustomAppDefaultToken <CustomAppDefaultToken
apiUri={apiUri} apiUrl={apiUrl}
token={token} token={token}
onApiUriClick={onApiUriClick} onApiUrlClick={onApiUrlClick}
onTokenClose={onTokenClose} onTokenClose={onTokenClose}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -5,7 +5,7 @@ import TokenDeleteDialog from "@saleor/apps/components/TokenDeleteDialog";
import { appMessages } from "@saleor/apps/messages"; import { appMessages } from "@saleor/apps/messages";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import { API_URI } from "@saleor/config"; import { getApiUrl } from "@saleor/config";
import { import {
AppTokenCreateMutation, AppTokenCreateMutation,
AppTokenDeleteMutation, AppTokenDeleteMutation,
@ -220,11 +220,11 @@ export const CustomAppDetails: React.FC<OrderListProps> = ({
<> <>
<WindowTitle title={getStringOrPlaceholder(customApp?.name)} /> <WindowTitle title={getStringOrPlaceholder(customApp?.name)} />
<CustomAppDetailsPage <CustomAppDetailsPage
apiUri={API_URI} apiUrl={getApiUrl()}
disabled={loading} disabled={loading}
errors={updateAppOpts.data?.appUpdate?.errors || []} errors={updateAppOpts.data?.appUpdate?.errors || []}
token={token} token={token}
onApiUriClick={() => open(API_URI, "blank")} onApiUrlClick={() => open(getApiUrl(), "blank")}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onTokenClose={onTokenClose} onTokenClose={onTokenClose}
onTokenCreate={() => openModal("create-token")} onTokenCreate={() => openModal("create-token")}

View file

@ -1,3 +1,4 @@
import { getApiUrl } from "@saleor/config";
import { createSaleorClient, SaleorProvider } from "@saleor/sdk"; import { createSaleorClient, SaleorProvider } from "@saleor/sdk";
import setupApi from "@test/api"; import setupApi from "@test/api";
import { act, renderHook } from "@testing-library/react-hooks"; import { act, renderHook } from "@testing-library/react-hooks";
@ -15,7 +16,7 @@ function renderAuthProvider() {
}; };
const notify = jest.fn(); const notify = jest.fn();
const saleorClient = createSaleorClient({ const saleorClient = createSaleorClient({
apiUrl: process.env.API_URI, apiUrl: getApiUrl(),
channel: "", channel: "",
}); });
const wrapper = ({ children }) => ( const wrapper = ({ children }) => (
@ -50,7 +51,7 @@ beforeEach(() => {
sessionStorage.clear(); sessionStorage.clear();
}); });
xdescribe("User", () => { describe("User", () => {
it("will be logged in if has valid credentials", async done => { it("will be logged in if has valid credentials", async done => {
const hook = renderAuthProvider(); const hook = renderAuthProvider();

View file

@ -3,7 +3,7 @@ import { Button } from "@saleor/components/Button";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import { IconButton } from "@saleor/components/IconButton"; import { IconButton } from "@saleor/components/IconButton";
import { APP_MOUNT_URI } from "@saleor/config"; import { getAppMountUri } from "@saleor/config";
import { RequestPasswordResetMutation } from "@saleor/graphql"; import { RequestPasswordResetMutation } from "@saleor/graphql";
import { SubmitPromise } from "@saleor/hooks/useForm"; import { SubmitPromise } from "@saleor/hooks/useForm";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
@ -36,7 +36,7 @@ const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
<Form initial={{ email: "" }} onSubmit={onSubmit}> <Form initial={{ email: "" }} onSubmit={onSubmit}>
{({ change: handleChange, data, submit: handleSubmit }) => ( {({ change: handleChange, data, submit: handleSubmit }) => (
<> <>
<IconButton className={classes.backBtn} href={APP_MOUNT_URI}> <IconButton className={classes.backBtn} href={getAppMountUri()}>
<ArrowRightIcon className={classes.arrow} /> <ArrowRightIcon className={classes.arrow} />
</IconButton> </IconButton>
<Typography variant="h3" className={classes.header}> <Typography variant="h3" className={classes.header}>

View file

@ -1,6 +1,6 @@
import { ApolloClient } from "@apollo/client"; import { ApolloClient } from "@apollo/client";
import { IMessageContext } from "@saleor/components/messages"; import { IMessageContext } from "@saleor/components/messages";
import { APP_DEFAULT_URI, APP_MOUNT_URI, DEMO_MODE } from "@saleor/config"; import { DEMO_MODE } from "@saleor/config";
import { useUserDetailsQuery } from "@saleor/graphql"; import { useUserDetailsQuery } from "@saleor/graphql";
import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useLocalStorage from "@saleor/hooks/useLocalStorage";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -16,6 +16,7 @@ import {
login as loginWithCredentialsManagementAPI, login as loginWithCredentialsManagementAPI,
saveCredentials, saveCredentials,
} from "@saleor/utils/credentialsManagement"; } from "@saleor/utils/credentialsManagement";
import { getAppMountUriForRedirect } from "@saleor/utils/urls";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { IntlShape } from "react-intl"; import { IntlShape } from "react-intl";
import urlJoin from "url-join"; import urlJoin from "url-join";
@ -88,8 +89,10 @@ export function useAuthProvider({
}); });
const handleLogout = async () => { const handleLogout = async () => {
const path = APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI; const returnTo = urlJoin(
const returnTo = urlJoin(window.location.origin, path); window.location.origin,
getAppMountUriForRedirect(),
);
const result = await logout({ const result = await logout({
input: JSON.stringify({ input: JSON.stringify({

View file

@ -1,7 +1,7 @@
import { APP_DEFAULT_URI, APP_MOUNT_URI } from "@saleor/config";
import { useAvailableExternalAuthenticationsQuery } from "@saleor/graphql"; import { useAvailableExternalAuthenticationsQuery } from "@saleor/graphql";
import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useLocalStorage from "@saleor/hooks/useLocalStorage";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import { getAppMountUriForRedirect } from "@saleor/utils/urls";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import urlJoin from "url-join"; import urlJoin from "url-join";
import useRouter from "use-react-router"; import useRouter from "use-react-router";
@ -52,7 +52,7 @@ const LoginView: React.FC<LoginViewProps> = ({ params }) => {
const result = await requestLoginByExternalPlugin(pluginId, { const result = await requestLoginByExternalPlugin(pluginId, {
redirectUri: urlJoin( redirectUri: urlJoin(
window.location.origin, window.location.origin,
APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI, getAppMountUriForRedirect(),
loginCallbackPath, loginCallbackPath,
), ),
}); });

View file

@ -1,8 +1,8 @@
import { APP_MOUNT_URI } from "@saleor/config";
import { useRequestPasswordResetMutation } from "@saleor/graphql"; import { useRequestPasswordResetMutation } from "@saleor/graphql";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { extractMutationErrors } from "@saleor/misc"; import { extractMutationErrors } from "@saleor/misc";
import { getAppMountUriForRedirect } from "@saleor/utils/urls";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import urlJoin from "url-join"; import urlJoin from "url-join";
@ -49,7 +49,7 @@ const ResetPasswordView: React.FC = () => {
email: data.email, email: data.email,
redirectUrl: urlJoin( redirectUrl: urlJoin(
window.location.origin, window.location.origin,
APP_MOUNT_URI === "/" ? "" : APP_MOUNT_URI, getAppMountUriForRedirect(),
newPasswordUrl().replace(/\?/, ""), newPasswordUrl().replace(/\?/, ""),
), ),
}, },

View file

@ -1,4 +1,4 @@
import { APP_MOUNT_URI } from "@saleor/config"; import { getAppMountUri } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import React from "react"; import React from "react";
@ -7,7 +7,7 @@ import ResetPasswordSuccessPage from "../components/ResetPasswordSuccessPage";
const ResetPasswordSuccessView: React.FC = () => { const ResetPasswordSuccessView: React.FC = () => {
const navigate = useNavigator(); const navigate = useNavigator();
return <ResetPasswordSuccessPage onBack={() => navigate(APP_MOUNT_URI)} />; return <ResetPasswordSuccessPage onBack={() => navigate(getAppMountUri())} />;
}; };
ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView"; ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView";
export default ResetPasswordSuccessView; export default ResetPasswordSuccessView;

View file

@ -2,10 +2,10 @@ import packageInfo from "../package.json";
import { SearchVariables } from "./hooks/makeSearch"; import { SearchVariables } from "./hooks/makeSearch";
import { ListSettings, ListViews, Pagination } from "./types"; import { ListSettings, ListViews, Pagination } from "./types";
export const IS_TEST = process.env.NODE_ENV === "test"; export const getAppDefaultUri = () => "/";
export const APP_MOUNT_URI = IS_TEST ? "/" : process.env.APP_MOUNT_URI || "/"; export const getAppMountUri = () =>
export const APP_DEFAULT_URI = "/"; window.__SALEOR_CONFIG__.APP_MOUNT_URI || getAppDefaultUri();
export const API_URI = process.env.API_URI; export const getApiUrl = () => window.__SALEOR_CONFIG__.API_URL;
export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL, 10); export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL, 10);
export const IS_CLOUD_INSTANCE = process.env.IS_CLOUD_INSTANCE === "true"; export const IS_CLOUD_INSTANCE = process.env.IS_CLOUD_INSTANCE === "true";
export const MARKETPLACE_URL = process.env.MARKETPLACE_URL; export const MARKETPLACE_URL = process.env.MARKETPLACE_URL;

View file

@ -4,7 +4,7 @@ import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
import { createFetch, createSaleorClient } from "@saleor/sdk"; import { createFetch, createSaleorClient } from "@saleor/sdk";
import { createUploadLink } from "apollo-upload-client"; import { createUploadLink } from "apollo-upload-client";
import { API_URI } from "../config"; import { getApiUrl } from "../config";
import introspectionQueryResultData from "./fragmentTypes.generated"; import introspectionQueryResultData from "./fragmentTypes.generated";
import { TypedTypePolicies } from "./typePolicies.generated"; import { TypedTypePolicies } from "./typePolicies.generated";
@ -21,7 +21,7 @@ const attachVariablesLink = new ApolloLink((operation, forward) =>
export const link = attachVariablesLink.concat( export const link = attachVariablesLink.concat(
createUploadLink({ createUploadLink({
credentials: "include", credentials: "include",
uri: API_URI, uri: getApiUrl(),
fetch: createFetch(), fetch: createFetch(),
}), }),
); );
@ -70,6 +70,6 @@ export const apolloClient = new ApolloClient({
}); });
export const saleorClient = createSaleorClient({ export const saleorClient = createSaleorClient({
apiUrl: API_URI, apiUrl: getApiUrl(),
channel: "", channel: "",
}); });

View file

@ -8,6 +8,12 @@
/> />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<title>Saleor e-commerce</title> <title>Saleor e-commerce</title>
<script>
window.__SALEOR_CONFIG__ = {
API_URL: "<%= API_URL %>",
APP_MOUNT_URI: "<%= APP_MOUNT_URI %>",
};
</script>
</head> </head>
<body> <body>

View file

@ -36,7 +36,7 @@ import { LocaleProvider } from "./components/Locale";
import MessageManagerProvider from "./components/messages"; import MessageManagerProvider from "./components/messages";
import { ShopProvider } from "./components/Shop"; import { ShopProvider } from "./components/Shop";
import { WindowTitle } from "./components/WindowTitle"; import { WindowTitle } from "./components/WindowTitle";
import { APP_MOUNT_URI, DEMO_MODE, GTM_ID } from "./config"; import { DEMO_MODE, getAppMountUri, GTM_ID } from "./config";
import ConfigurationSection from "./configuration"; import ConfigurationSection from "./configuration";
import { getConfigMenuItemsPermissions } from "./configuration/utils"; import { getConfigMenuItemsPermissions } from "./configuration/utils";
import AppStateProvider from "./containers/AppState"; import AppStateProvider from "./containers/AppState";
@ -80,7 +80,7 @@ errorTracker.init();
const App: React.FC = () => ( const App: React.FC = () => (
<SaleorProvider client={saleorClient}> <SaleorProvider client={saleorClient}>
<ApolloProvider client={apolloClient}> <ApolloProvider client={apolloClient}>
<BrowserRouter basename={APP_MOUNT_URI}> <BrowserRouter basename={getAppMountUri()}>
<ThemeProvider overrides={themeOverrides}> <ThemeProvider overrides={themeOverrides}>
<DateProvider> <DateProvider>
<LocaleProvider> <LocaleProvider>

View file

@ -4,7 +4,7 @@ import SaveFilterTabDialog, {
SaveFilterTabDialogFormData, SaveFilterTabDialogFormData,
} from "@saleor/components/SaveFilterTabDialog"; } from "@saleor/components/SaveFilterTabDialog";
import { useShopLimitsQuery } from "@saleor/components/Shop/queries"; import { useShopLimitsQuery } from "@saleor/components/Shop/queries";
import { APP_MOUNT_URI, DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { useStaffListQuery, useStaffMemberAddMutation } from "@saleor/graphql"; import { useStaffListQuery, useStaffMemberAddMutation } from "@saleor/graphql";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -23,6 +23,7 @@ import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createSortHandler from "@saleor/utils/handlers/sortHandler";
import { mapEdgesToItems } from "@saleor/utils/maps"; import { mapEdgesToItems } from "@saleor/utils/maps";
import { getSortParams } from "@saleor/utils/sort"; import { getSortParams } from "@saleor/utils/sort";
import { getAppMountUriForRedirect } from "@saleor/utils/urls";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import urlJoin from "url-join"; import urlJoin from "url-join";
@ -159,7 +160,7 @@ export const StaffList: React.FC<StaffListProps> = ({ params }) => {
lastName: variables.lastName, lastName: variables.lastName,
redirectUrl: urlJoin( redirectUrl: urlJoin(
window.location.origin, window.location.origin,
APP_MOUNT_URI === "/" ? "" : APP_MOUNT_URI, getAppMountUriForRedirect(),
newPasswordUrl().replace(/\?/, ""), newPasswordUrl().replace(/\?/, ""),
), ),
}, },

View file

@ -10,7 +10,7 @@ import { ApolloMockedProvider } from "../../testUtils/ApolloMockedProvider";
import { Provider as DateProvider } from "../components/Date/DateContext"; import { Provider as DateProvider } from "../components/Date/DateContext";
import MessageManagerProvider from "../components/messages"; import MessageManagerProvider from "../components/messages";
import { TimezoneProvider } from "../components/Timezone"; import { TimezoneProvider } from "../components/Timezone";
import { APP_MOUNT_URI } from "../config"; import { getAppMountUri } from "../config";
export const Decorator = storyFn => ( export const Decorator = storyFn => (
<ApolloMockedProvider> <ApolloMockedProvider>
@ -24,7 +24,7 @@ export const Decorator = storyFn => (
<DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}> <DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}>
<TimezoneProvider value="America/New_York"> <TimezoneProvider value="America/New_York">
<ThemeProvider overrides={themeOverrides}> <ThemeProvider overrides={themeOverrides}>
<BrowserRouter basename={APP_MOUNT_URI}> <BrowserRouter basename={getAppMountUri()}>
<ExternalAppProvider> <ExternalAppProvider>
<MessageManagerProvider> <MessageManagerProvider>
<div <div

View file

@ -1,3 +1,4 @@
import { getAppDefaultUri, getAppMountUri } from "@saleor/config";
import isArray from "lodash/isArray"; import isArray from "lodash/isArray";
import { stringify } from "qs"; import { stringify } from "qs";
@ -20,3 +21,6 @@ export function getArrayQueryParam(param: string | string[]): string[] {
} }
export const isExternalURL = url => /^https?:\/\//.test(url); export const isExternalURL = url => /^https?:\/\//.test(url);
export const getAppMountUriForRedirect = () =>
getAppMountUri() === getAppDefaultUri() ? "" : getAppMountUri();

View file

@ -3,6 +3,7 @@ import { BatchHttpLink } from "@apollo/client/link/batch-http";
import NodeHttpAdapter from "@pollyjs/adapter-node-http"; import NodeHttpAdapter from "@pollyjs/adapter-node-http";
import { Polly } from "@pollyjs/core"; import { Polly } from "@pollyjs/core";
import FSPersister from "@pollyjs/persister-fs"; import FSPersister from "@pollyjs/persister-fs";
import { getApiUrl } from "@saleor/config";
import { createFetch } from "@saleor/sdk"; import { createFetch } from "@saleor/sdk";
import path from "path"; import path from "path";
import { setupPolly } from "setup-polly-jest"; import { setupPolly } from "setup-polly-jest";
@ -36,7 +37,7 @@ function setupApi() {
const cache = new InMemoryCache(); const cache = new InMemoryCache();
const link = new BatchHttpLink({ const link = new BatchHttpLink({
fetch: createFetch(), fetch: createFetch(),
uri: process.env.API_URI || "http://localhost:8000/graphql/", uri: getApiUrl(),
}); });
const apolloClient = new ApolloClient({ const apolloClient = new ApolloClient({
cache, cache,

View file

@ -1 +1,6 @@
document.getElementById = () => document.createElement("div"); document.getElementById = () => document.createElement("div");
window.__SALEOR_CONFIG__ = {
API_URL: "http://localhost:8000/graphql/",
APP_MOUNT_URI: "/",
};

View file

@ -35,12 +35,16 @@ const htmlWebpackPlugin = new HtmlWebpackPlugin({
filename: "index.html", filename: "index.html",
hash: true, hash: true,
template: "./src/index.html", template: "./src/index.html",
templateParameters: {
// URI is kept for backwards compatibility.
// See more at https://github.com/saleor/saleor-dashboard/issues/2502
API_URL: process.env.API_URI,
APP_MOUNT_URI: process.env.APP_MOUNT_URI,
},
}); });
const environmentPlugin = new webpack.EnvironmentPlugin({ const environmentPlugin = new webpack.EnvironmentPlugin({
API_URI: "",
MARKETPLACE_URL: "", MARKETPLACE_URL: "",
SALEOR_APPS_ENDPOINT: "", SALEOR_APPS_ENDPOINT: "",
APP_MOUNT_URI: "/",
DEMO_MODE: false, DEMO_MODE: false,
ENVIRONMENT: "", ENVIRONMENT: "",
GTM_ID: "", GTM_ID: "",

4
webpack.d.ts vendored
View file

@ -7,4 +7,8 @@ declare module "*.svg" {
declare interface Window { declare interface Window {
PasswordCredential: PasswordCredential; PasswordCredential: PasswordCredential;
__SALEOR_CONFIG__: {
API_URL: string;
APP_MOUNT_URI: string;
};
} }