Service environment (#23443)
* fix fileutils.TestFindFile on MacOS * introduce model.ExternalServiceEnvironment * pick license public key from external service env * pick Stripe public key from external service env * pick Rudder key from external service env * configure Sentry DSN from external service env * always log external_service_environment, Unsetenv * clear faked BuildEnv, improve logging * strip out unset GOTAGS * fix Sentry tests * simplify to just ServiceEnvironment * relocate ServiceEnvironment in client config * initialize CWS URLs based on service environment * unset rudder key for boards dev * harden service environment to avoid accidental production * fix TestSentry again * fix DEFAULT -> ENTERPRISE * s/dev/test when naming playbooks rudder key * simplify boards rudder key switch * use uniform rudderKey variable names * retain compatibility with existing pipeline * reduce to just production/test * unit test with valid test license * simplify Playbooks telemetry initialization * restore dev service environment * emit ServiceEnvironment when running e2e tests
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6c82605df0
Коммит
305fac6507
@@ -12,6 +12,8 @@ import {SuiteWindow} from 'src/types/index'
|
||||
|
||||
import {PluginRegistry} from 'src/types/mattermost-webapp'
|
||||
|
||||
import {ServiceEnvironment} from '@mattermost/types/config'
|
||||
|
||||
import {RudderTelemetryHandler, rudderAnalytics} from 'src/rudder'
|
||||
|
||||
import appBarIcon from 'static/app-bar-icon.png'
|
||||
@@ -83,8 +85,19 @@ function getSubpath(siteURL: string): string {
|
||||
return url.pathname.replace(/\/+$/, '')
|
||||
}
|
||||
|
||||
const TELEMETRY_RUDDER_KEY = 'placeholder_boards_rudder_key'
|
||||
const TELEMETRY_RUDDER_DATAPLANE_URL = 'placeholder_rudder_dataplane_url'
|
||||
const TELEMETRY_RUDDER_URL = 'https://pdat.matterlytics.com'
|
||||
const TELEMETRY_RUDDER_KEY_PROD = '1myWcDbTkIThnpPYyms7DKlmQWl'
|
||||
const TELEMETRY_RUDDER_KEY_TEST = '1myWYwHRDFdLDTpznQ7qFlOPQaa'
|
||||
|
||||
// TO_BE_DEPRECATED_* are placeholders to allow the existing release pipelines to run without
|
||||
// failing to insert the values that are now hard-coded above. Remove this once we converge
|
||||
// on the unified delivery pipeline in GitHub.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const TO_BE_DEPRECATED_TELEMETRY_RUDDER_URL = 'placeholder_rudder_dataplane_url'
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const TO_BE_DEPRECATAED_TELEMETRY_RUDDER_KEY = 'placeholder_boards_rudder_key'
|
||||
|
||||
const TELEMETRY_OPTIONS = {
|
||||
context: {
|
||||
ip: '0.0.0.0',
|
||||
@@ -512,12 +525,17 @@ export default class Plugin {
|
||||
|
||||
const config = await octoClient.getClientConfig()
|
||||
if (config?.telemetry) {
|
||||
let rudderKey = TELEMETRY_RUDDER_KEY
|
||||
let rudderUrl = TELEMETRY_RUDDER_DATAPLANE_URL
|
||||
|
||||
if (rudderKey.startsWith('placeholder') && rudderUrl.startsWith('placeholder')) {
|
||||
rudderKey = process.env.RUDDER_KEY as string //eslint-disable-line no-process-env
|
||||
rudderUrl = process.env.RUDDER_DATAPLANE_URL as string //eslint-disable-line no-process-env
|
||||
const rudderUrl = TELEMETRY_RUDDER_URL
|
||||
let rudderKey = ''
|
||||
switch (mmStore.getState().entities.general.config.ServiceEnvironment) {
|
||||
case ServiceEnvironment.PRODUCTION:
|
||||
rudderKey = TELEMETRY_RUDDER_KEY_PROD
|
||||
break
|
||||
case ServiceEnvironment.TEST:
|
||||
rudderKey = TELEMETRY_RUDDER_KEY_TEST
|
||||
break
|
||||
case ServiceEnvironment.DEV:
|
||||
break
|
||||
}
|
||||
|
||||
if (rudderKey !== '') {
|
||||
|
||||
@@ -172,15 +172,6 @@ config.output = {
|
||||
chunkFilename: '[name].[contenthash].js',
|
||||
};
|
||||
|
||||
/* eslint-disable no-process-env */
|
||||
const env = {};
|
||||
env.RUDDER_KEY = JSON.stringify(process.env.RUDDER_KEY || '');
|
||||
env.RUDDER_DATAPLANE_URL = JSON.stringify(process.env.RUDDER_DATAPLANE_URL || '');
|
||||
|
||||
config.plugins.push(new webpack.DefinePlugin({
|
||||
'process.env': env,
|
||||
}));
|
||||
|
||||
config.plugins.push(new MiniCssExtractPlugin({
|
||||
filename: '[name].[contenthash].css',
|
||||
chunkFilename: '[name].[contenthash].css',
|
||||
|
||||
@@ -25,7 +25,7 @@ import {CloudLinks} from 'utils/constants';
|
||||
import BlockableLink from 'components/admin_console/blockable_link';
|
||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||
import PaymentForm from 'components/payment_form/payment_form';
|
||||
import {STRIPE_CSS_SRC, STRIPE_PUBLIC_KEY} from 'components/payment_form/stripe';
|
||||
import {STRIPE_CSS_SRC, getStripePublicKey} from 'components/payment_form/stripe';
|
||||
import SaveButton from 'components/save_button';
|
||||
import AlertBanner from 'components/alert_banner';
|
||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||
@@ -58,6 +58,8 @@ const PaymentInfoEdit: React.FC = () => {
|
||||
card: {} as any,
|
||||
});
|
||||
|
||||
const stripePublicKey = useSelector((state: GlobalState) => getStripePublicKey(state));
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getCloudCustomer());
|
||||
}, []);
|
||||
@@ -83,7 +85,7 @@ const PaymentInfoEdit: React.FC = () => {
|
||||
};
|
||||
|
||||
if (!stripePromise) {
|
||||
stripePromise = loadStripe(STRIPE_PUBLIC_KEY);
|
||||
stripePromise = loadStripe(stripePublicKey);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {useSelector} from 'react-redux';
|
||||
import {Stripe} from '@stripe/stripe-js';
|
||||
|
||||
import {loadStripe} from '@stripe/stripe-js/pure'; // https://github.com/stripe/stripe-js#importing-loadstripe-without-side-effects
|
||||
|
||||
import {STRIPE_PUBLIC_KEY} from 'components/payment_form/stripe';
|
||||
import {GlobalState} from 'types/store';
|
||||
|
||||
import {getStripePublicKey} from 'components/payment_form/stripe';
|
||||
|
||||
// reloadHint
|
||||
export default function useLoadStripe(reloadHint?: number) {
|
||||
const stripeRef = useRef<Stripe | null>(null);
|
||||
const [, setDone] = useState(false);
|
||||
const stripePublicKey = useSelector((state: GlobalState) => getStripePublicKey(state));
|
||||
|
||||
useEffect(() => {
|
||||
if (stripeRef.current) {
|
||||
return;
|
||||
}
|
||||
loadStripe(STRIPE_PUBLIC_KEY).then((stripe: Stripe | null) => {
|
||||
loadStripe(stripePublicKey).then((stripe: Stripe | null) => {
|
||||
stripeRef.current = stripe;
|
||||
|
||||
// deliberately cause a rerender so that the input can render.
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
SetupIntent,
|
||||
} from '@stripe/stripe-js';
|
||||
|
||||
import {GlobalState} from 'types/store';
|
||||
import {ServiceEnvironment} from '@mattermost/types/config';
|
||||
|
||||
type ConfirmCardSetupType = (clientSecret: string, data?: ConfirmCardSetupData | undefined, options?: ConfirmCardSetupOptions | undefined) => Promise<{ setupIntent?: SetupIntent | undefined; error?: StripeError | undefined }> | undefined;
|
||||
|
||||
function prodConfirmCardSetup(confirmCardSetup: ConfirmCardSetupType): ConfirmCardSetupType {
|
||||
@@ -26,4 +29,15 @@ export const getConfirmCardSetup = (isCwsMockMode?: boolean) => (isCwsMockMode ?
|
||||
|
||||
export const STRIPE_CSS_SRC = 'https://fonts.googleapis.com/css?family=Open+Sans:400,400i,600,600i&display=swap';
|
||||
//eslint-disable-next-line no-process-env
|
||||
export const STRIPE_PUBLIC_KEY = process.env.STRIPE_PUBLIC_KEY || 'pk_test_ttEpW6dCHksKyfAFzh6MvgBj';
|
||||
|
||||
export const getStripePublicKey = (state: GlobalState) => {
|
||||
switch (state.entities.general.config.ServiceEnvironment) {
|
||||
case ServiceEnvironment.PRODUCTION:
|
||||
return 'pk_live_cDF5gYLPf5vQjJ7jp71p7GRK';
|
||||
case ServiceEnvironment.TEST:
|
||||
case ServiceEnvironment.DEV:
|
||||
return 'pk_test_ttEpW6dCHksKyfAFzh6MvgBj';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
@@ -30,6 +30,8 @@ import withGetCloudSubscription from 'components/common/hocs/cloud/with_get_clou
|
||||
import {findOnlyYearlyProducts} from 'utils/products';
|
||||
import {getCloudContactSalesLink, getCloudSupportLink} from 'utils/contact_support_sales';
|
||||
|
||||
import {getStripePublicKey} from 'components/payment_form/stripe';
|
||||
|
||||
const PurchaseModal = makeAsyncComponent('PurchaseModal', React.lazy(() => import('./purchase_modal')));
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
@@ -46,6 +48,7 @@ function mapStateToProps(state: GlobalState) {
|
||||
const companyName = customer?.name || '';
|
||||
const contactSalesLink = getCloudContactSalesLink(firstName, lastName, companyName, customerEmail, 'mattermost', 'in-product-cloud');
|
||||
const contactSupportLink = getCloudSupportLink(customerEmail, 'Cloud purchase', '', window.location.host);
|
||||
const stripePublicKey = getStripePublicKey(state);
|
||||
|
||||
return {
|
||||
show: isModalOpen(state, ModalIdentifiers.CLOUD_PURCHASE),
|
||||
@@ -64,6 +67,7 @@ function mapStateToProps(state: GlobalState) {
|
||||
theme: getTheme(state),
|
||||
isDelinquencyModal,
|
||||
usersCount: Number(getAdminAnalytics(state)!.TOTAL_USERS) || 1,
|
||||
stripePublicKey,
|
||||
};
|
||||
}
|
||||
type Actions = {
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
import {goToMattermostContactSalesForm} from 'utils/contact_support_sales';
|
||||
|
||||
import PaymentDetails from 'components/admin_console/billing/payment_details';
|
||||
import {STRIPE_CSS_SRC, STRIPE_PUBLIC_KEY} from 'components/payment_form/stripe';
|
||||
import {STRIPE_CSS_SRC} from 'components/payment_form/stripe';
|
||||
import RootPortal from 'components/root_portal';
|
||||
import FullScreenModal from 'components/widgets/modals/full_screen_modal';
|
||||
import OverlayTrigger from 'components/overlay_trigger';
|
||||
@@ -130,6 +130,8 @@ type Props = {
|
||||
// callerCTA is information about the cta that opened this modal. This helps us provide a telemetry path
|
||||
// showing information about how the modal was opened all the way to more CTAs within the modal itself
|
||||
callerCTA?: string;
|
||||
|
||||
stripePublicKey: string;
|
||||
actions: {
|
||||
openModal: <P>(modalData: ModalData<P>) => void;
|
||||
closeModal: () => void;
|
||||
@@ -964,7 +966,7 @@ class PurchaseModal extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
if (!stripePromise) {
|
||||
stripePromise = loadStripe(STRIPE_PUBLIC_KEY);
|
||||
stripePromise = loadStripe(this.props.stripePublicKey);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,9 @@ import * as GlobalActions from 'actions/global_actions';
|
||||
import Constants, {StoragePrefixes, WindowSizes} from 'utils/constants';
|
||||
import matchMedia from 'tests/helpers/match_media.mock';
|
||||
import {ProductComponent} from 'types/store/plugins';
|
||||
import {ServiceEnvironment} from '@mattermost/types/config';
|
||||
|
||||
import store from 'stores/redux_store.jsx';
|
||||
|
||||
jest.mock('rudder-sdk-js', () => ({
|
||||
identify: jest.fn(),
|
||||
@@ -201,12 +204,16 @@ describe('components/Root', () => {
|
||||
describe('onConfigLoaded', () => {
|
||||
afterEach(() => {
|
||||
Client4.telemetryHandler = undefined;
|
||||
|
||||
Constants.TELEMETRY_RUDDER_KEY = 'placeholder_rudder_key';
|
||||
Constants.TELEMETRY_RUDDER_DATAPLANE_URL = 'placeholder_rudder_dataplane_url';
|
||||
});
|
||||
|
||||
test('should not set a TelemetryHandler when onConfigLoaded is called if Rudder is not configured', () => {
|
||||
store.dispatch({
|
||||
type: GeneralTypes.CLIENT_CONFIG_RECEIVED,
|
||||
data: {
|
||||
ServiceEnvironment: ServiceEnvironment.DEV,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = shallow(<Root {...baseProps}/>);
|
||||
|
||||
Client4.trackEvent('category', 'event');
|
||||
@@ -217,8 +224,12 @@ describe('components/Root', () => {
|
||||
});
|
||||
|
||||
test('should set a TelemetryHandler when onConfigLoaded is called if Rudder is configured', () => {
|
||||
Constants.TELEMETRY_RUDDER_KEY = 'testKey';
|
||||
Constants.TELEMETRY_RUDDER_DATAPLANE_URL = 'url';
|
||||
store.dispatch({
|
||||
type: GeneralTypes.CLIENT_CONFIG_RECEIVED,
|
||||
data: {
|
||||
ServiceEnvironment: ServiceEnvironment.TEST,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = shallow(<Root {...baseProps}/>);
|
||||
|
||||
@@ -236,8 +247,12 @@ describe('components/Root', () => {
|
||||
// Simulate an error occurring and the callback not getting called
|
||||
});
|
||||
|
||||
Constants.TELEMETRY_RUDDER_KEY = 'testKey';
|
||||
Constants.TELEMETRY_RUDDER_DATAPLANE_URL = 'url';
|
||||
store.dispatch({
|
||||
type: GeneralTypes.CLIENT_CONFIG_RECEIVED,
|
||||
data: {
|
||||
ServiceEnvironment: ServiceEnvironment.TEST,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = shallow(<Root {...baseProps}/>);
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ import {applyLuxonDefaults} from './effects';
|
||||
|
||||
import RootProvider from './root_provider';
|
||||
import RootRedirect from './root_redirect';
|
||||
import {ServiceEnvironment} from '@mattermost/types/config';
|
||||
|
||||
const CreateTeam = makeAsyncComponent('CreateTeam', LazyCreateTeam);
|
||||
const ErrorPage = makeAsyncComponent('ErrorPage', LazyErrorPage);
|
||||
@@ -229,17 +230,23 @@ export default class Root extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
onConfigLoaded = () => {
|
||||
const config = getConfig(store.getState());
|
||||
const telemetryId = this.props.telemetryId;
|
||||
|
||||
let rudderKey: string | null | undefined = Constants.TELEMETRY_RUDDER_KEY;
|
||||
let rudderUrl: string | null | undefined = Constants.TELEMETRY_RUDDER_DATAPLANE_URL;
|
||||
|
||||
if (rudderKey.startsWith('placeholder') && rudderUrl.startsWith('placeholder')) {
|
||||
rudderKey = process.env.RUDDER_KEY; //eslint-disable-line no-process-env
|
||||
rudderUrl = process.env.RUDDER_DATAPLANE_URL; //eslint-disable-line no-process-env
|
||||
const rudderUrl = 'https://pdat.matterlytics.com';
|
||||
let rudderKey = '';
|
||||
switch (config.ServiceEnvironment) {
|
||||
case ServiceEnvironment.PRODUCTION:
|
||||
rudderKey = '1aoejPqhgONMI720CsBSRWzzRQ9';
|
||||
break;
|
||||
case ServiceEnvironment.TEST:
|
||||
rudderKey = '1aoeoCDeh7OCHcbW2kseWlwUFyq';
|
||||
break;
|
||||
case ServiceEnvironment.DEV:
|
||||
break;
|
||||
}
|
||||
|
||||
if (rudderKey != null && rudderKey !== '' && this.props.telemetryEnabled) {
|
||||
if (rudderKey !== '' && this.props.telemetryEnabled) {
|
||||
const rudderCfg: {setCookieDomain?: string} = {};
|
||||
const siteURL = getConfig(store.getState()).SiteURL;
|
||||
if (siteURL !== '') {
|
||||
|
||||
@@ -1974,8 +1974,6 @@ export const Constants = {
|
||||
AUTOCOMPLETE_SPLIT_CHARACTERS: ['.', '-', '_'],
|
||||
ANIMATION_TIMEOUT: 1000,
|
||||
SEARCH_TIMEOUT_MILLISECONDS: 100,
|
||||
TELEMETRY_RUDDER_KEY: 'placeholder_rudder_key',
|
||||
TELEMETRY_RUDDER_DATAPLANE_URL: 'placeholder_rudder_dataplane_url',
|
||||
TEAMMATE_NAME_DISPLAY: {
|
||||
SHOW_USERNAME: 'username',
|
||||
SHOW_NICKNAME_FULLNAME: 'nickname_full_name',
|
||||
|
||||
@@ -410,20 +410,14 @@ if (DEV) {
|
||||
config.devtool = 'source-map';
|
||||
}
|
||||
|
||||
const env = {
|
||||
STRIPE_PUBLIC_KEY: JSON.stringify(process.env.STRIPE_PUBLIC_KEY || ''),
|
||||
};
|
||||
const env = {};
|
||||
if (DEV) {
|
||||
env.PUBLIC_PATH = JSON.stringify(publicPath);
|
||||
env.RUDDER_KEY = JSON.stringify(process.env.RUDDER_KEY || '');
|
||||
env.RUDDER_DATAPLANE_URL = JSON.stringify(process.env.RUDDER_DATAPLANE_URL || '');
|
||||
if (process.env.MM_LIVE_RELOAD) {
|
||||
config.plugins.push(new LiveReloadPlugin());
|
||||
}
|
||||
} else {
|
||||
env.NODE_ENV = JSON.stringify('production');
|
||||
env.RUDDER_KEY = JSON.stringify(process.env.RUDDER_KEY || '');
|
||||
env.RUDDER_DATAPLANE_URL = JSON.stringify(process.env.RUDDER_DATAPLANE_URL || '');
|
||||
}
|
||||
|
||||
config.plugins.push(new webpack.DefinePlugin({
|
||||
|
||||
@@ -203,6 +203,7 @@ export type ClientConfig = {
|
||||
PersistentNotificationIntervalMinutes: string;
|
||||
AllowPersistentNotificationsForGuests: string;
|
||||
DelayChannelAutocomplete: 'true' | 'false';
|
||||
ServiceEnvironment: string;
|
||||
};
|
||||
|
||||
export type License = {
|
||||
@@ -948,3 +949,9 @@ export enum CollapsedThreads {
|
||||
DEFAULT_OFF = 'default_off',
|
||||
ALWAYS_ON = 'always_on',
|
||||
}
|
||||
|
||||
export enum ServiceEnvironment {
|
||||
PRODUCTION = 'production',
|
||||
TEST = 'test',
|
||||
DEV = 'dev',
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user