chore: telemetry localization (#6075)

This commit is contained in:
Elliot DeNolf
2024-04-28 22:17:08 -04:00
committed by GitHub
parent bed428c27e
commit a7096c1599
20 changed files with 83 additions and 24 deletions

View File

@@ -27,6 +27,7 @@ export const nodemailerAdapter = async (
const { defaultFromAddress, defaultFromName, transport } = await buildEmail(args)
const adapter: NodemailerAdapter = () => ({
name: 'nodemailer',
defaultFromAddress,
defaultFromName,
sendEmail: async (message) => {

View File

@@ -17,7 +17,7 @@ import { isPlainObject } from '../../utilities/isPlainObject.js'
import baseVersionFields from '../../versions/baseFields.js'
import { authDefaults, defaults } from './defaults.js'
const sanitizeCollection = (
export const sanitizeCollection = (
config: Config,
collection: CollectionConfig,
): SanitizedCollectionConfig => {
@@ -159,5 +159,3 @@ const sanitizeCollection = (
return sanitized as SanitizedCollectionConfig
}
export default sanitizeCollection

View File

@@ -152,6 +152,7 @@ const collectionSchema = joi.object().keys({
}),
upload: joi.alternatives().try(
joi.object({
adapter: joi.string(),
adminThumbnail: joi.alternatives().try(joi.string(), componentSchema),
crop: joi.bool(),
disableLocalStorage: joi.bool(),

View File

@@ -9,7 +9,7 @@ import type {
} from './types.js'
import { defaultUserCollection } from '../auth/defaultUser.js'
import sanitizeCollection from '../collections/config/sanitize.js'
import { sanitizeCollection } from '../collections/config/sanitize.js'
import { migrationsCollection } from '../database/migrations/migrationsCollection.js'
import { InvalidConfiguration } from '../errors/index.js'
import sanitizeGlobals from '../globals/config/sanitize.js'
@@ -110,5 +110,11 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
config.csrf.push(config.serverURL)
}
// Get deduped list of upload adapters
if (!config.upload) config.upload = { adapters: [] }
config.upload.adapters = Array.from(
new Set(config.collections.map((c) => c.upload?.adapter).filter(Boolean)),
)
return config as SanitizedConfig
}

View File

@@ -640,7 +640,7 @@ export type Config = {
export type SanitizedConfig = Omit<
DeepRequired<Config>,
'collections' | 'endpoint' | 'globals' | 'i18n' | 'localization'
'collections' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
> & {
collections: SanitizedCollectionConfig[]
endpoints: Endpoint[]
@@ -652,6 +652,12 @@ export type SanitizedConfig = Omit<
configDir: string
rawConfig: string
}
upload: ExpressFileUploadOptions & {
/**
* Deduped list of adapters used in the project
*/
adapters: string[]
}
}
export type EditConfig =

View File

@@ -4,6 +4,7 @@ import { emailDefaults } from './defaults.js'
import { getStringifiedToAddress } from './getStringifiedToAddress.js'
export const consoleEmailAdapter: EmailAdapter<void> = ({ payload }) => ({
name: 'console',
defaultFromAddress: emailDefaults.defaultFromAddress,
defaultFromName: emailDefaults.defaultFromName,
sendEmail: async (message) => {

View File

@@ -27,5 +27,6 @@ export type InitializedEmailAdapter<TSendEmailResponse = unknown> = ReturnType<
export type EmailAdapter<TSendEmailResponse = unknown> = ({ payload }: { payload: Payload }) => {
defaultFromAddress: string
defaultFromName: string
name: string
sendEmail: (message: SendEmailOptions) => Promise<TSendEmailResponse>
}

View File

@@ -70,6 +70,10 @@ export type ImageSize = Omit<ResizeOptions, 'withoutEnlargement'> & {
export type GetAdminThumbnail = (args: { doc: Record<string, unknown> }) => false | null | string
export type UploadConfig = {
/**
* The adapter to use for uploads.
*/
adapter?: string
/**
* Represents an admin thumbnail, which can be either a React component or a string.
* - If a string, it should be one of the image size names.

View File

@@ -17,13 +17,18 @@ const Conf = (ConfImport.default || ConfImport) as unknown as typeof ConfImport.
export type BaseEvent = {
ciName: null | string
dbAdapter: string
emailAdapter: null | string
envID: string
isCI: boolean
locales: string[]
localizationDefaultLocale: null | string
localizationEnabled: boolean
nodeEnv: string
nodeVersion: string
payloadPackages: Record<string, string>
payloadVersion: string
projectID: string
uploadAdapters: string[]
}
type PackageJSON = {
@@ -42,7 +47,7 @@ let baseEvent: BaseEvent | null = null
export const sendEvent = async ({ event, payload }: Args): Promise<void> => {
try {
const packageJSON = await getPackageJSON()
const { packageJSON, packageJSONPath } = await getPackageJSON()
// Only generate the base event once
if (!baseEvent) {
@@ -52,15 +57,18 @@ export const sendEvent = async ({ event, payload }: Args): Promise<void> => {
isCI: ciInfo.isCI,
nodeEnv: process.env.NODE_ENV || 'development',
nodeVersion: process.version,
payloadPackages: getPayloadPackages(packageJSON),
payloadVersion: getPayloadVersion(packageJSON),
projectID: getProjectID(payload, packageJSON),
...getLocalizationInfo(payload),
dbAdapter: payload.db.name,
emailAdapter: payload.email?.name || null,
uploadAdapters: payload.config.upload.adapters,
}
}
if (process.env.PAYLOAD_TELEMETRY_DEBUG) {
payload.logger.info({
event: { ...baseEvent, ...event },
event: { ...baseEvent, ...event, packageJSONPath },
msg: 'Telemetry Event',
})
return
@@ -120,18 +128,23 @@ const getGitID = (payload: Payload) => {
}
}
const getPayloadPackages = (packageJSON: PackageJSON): Record<string, string> => {
return Object.keys(packageJSON.dependencies || {}).reduce((acc, key) => {
return key.startsWith('@payloadcms/') ? { ...acc, [key]: packageJSON.dependencies[key] } : acc
}, {})
}
const getPackageJSON = async (): Promise<{
packageJSON?: PackageJSON
packageJSONPath: string
}> => {
let packageJSONPath = path.resolve(process.cwd(), 'package.json')
const getPackageJSON = async (): Promise<PackageJSON> => {
if (!fs.existsSync(packageJSONPath)) {
// Old logic
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const packageJsonPath = await findUp('package.json', { cwd: dirname })
const jsonContent: PackageJSON = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
return jsonContent
packageJSONPath = await findUp('package.json', { cwd: dirname })
const jsonContent: PackageJSON = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8'))
return { packageJSON: jsonContent, packageJSONPath }
}
const packageJSON: PackageJSON = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8'))
return { packageJSON, packageJSONPath }
}
const getPackageJSONID = (payload: Payload, packageJSON: PackageJSON): string => {
@@ -141,3 +154,21 @@ const getPackageJSONID = (payload: Payload, packageJSON: PackageJSON): string =>
export const getPayloadVersion = (packageJSON: PackageJSON): string => {
return packageJSON?.dependencies?.payload ?? ''
}
export const getLocalizationInfo = (
payload: Payload,
): Pick<BaseEvent, 'locales' | 'localizationDefaultLocale' | 'localizationEnabled'> => {
if (!payload.config.localization) {
return {
locales: [],
localizationDefaultLocale: null,
localizationEnabled: false,
}
}
return {
locales: payload.config.localization.localeCodes,
localizationDefaultLocale: payload.config.localization.defaultLocale,
localizationEnabled: true,
}
}

View File

@@ -57,6 +57,7 @@ export const azureBlobStorageAdapter = ({
return ({ collection, prefix }): GeneratedAdapter => {
return {
name: 'azure',
generateURL: getGenerateURL({ baseURL, containerName }),
handleDelete: getHandleDelete({ collection, getStorageClient }),
handleUpload: getHandleUpload({

View File

@@ -48,6 +48,7 @@ export const gcsAdapter =
}
return {
name: 'gcs',
generateURL: getGenerateURL({ bucket, getStorageClient }),
handleDelete: getHandleDelete({ bucket, getStorageClient }),
handleUpload: getHandleUpload({

View File

@@ -54,6 +54,7 @@ export const s3Adapter =
}
return {
name: 's3',
generateURL: getGenerateURL({ bucket, config }),
handleDelete: getHandleDelete({ bucket, getStorageClient }),
handleUpload: getHandleUpload({

View File

@@ -72,6 +72,7 @@ export const vercelBlobAdapter =
const baseUrl = `https://${storeId}.${access}.blob.vercel-storage.com`
return {
name: 'vercel-blob',
generateURL: getGenerateUrl({ baseUrl, prefix }),
handleDelete: getHandleDelete({ baseUrl, prefix, token }),
handleUpload: getHandleUpload({

View File

@@ -25,10 +25,10 @@ export const getAfterDeleteHook = ({
await Promise.all(promises)
} catch (err: unknown) {
req.payload.logger.error(
`There was an error while deleting files corresponding to the ${collection.labels?.singular} with ID ${doc.id}:`,
)
req.payload.logger.error(err)
req.payload.logger.error({
err,
msg: `There was an error while deleting files corresponding to the ${collection.labels?.singular} with ID ${doc.id}.`,
})
}
return doc
}

View File

@@ -76,6 +76,7 @@ export const cloudStorage =
},
upload: {
...(typeof existingCollection.upload === 'object' ? existingCollection.upload : {}),
adapter: adapter.name,
disableLocalStorage:
typeof options.disableLocalStorage === 'boolean'
? options.disableLocalStorage

View File

@@ -43,6 +43,7 @@ export interface GeneratedAdapter {
generateURL: GenerateURL
handleDelete: HandleDelete
handleUpload: HandleUpload
name: string
onInit?: () => void
staticHandler: StaticHandler
}

View File

@@ -120,6 +120,7 @@ function azureStorageInternal({
return ({ collection, prefix }): GeneratedAdapter => {
return {
name: 'azure',
generateURL: getGenerateURL({ baseURL, containerName }),
handleDelete: getHandleDelete({ collection, getStorageClient }),
handleUpload: getHandleUpload({

View File

@@ -100,6 +100,7 @@ function gcsStorageInternal({ acl, bucket, options }: GcsStorageOptions): Adapte
}
return {
name: 'gcs',
generateURL: getGenerateURL({ bucket, getStorageClient }),
handleDelete: getHandleDelete({ bucket, getStorageClient }),
handleUpload: getHandleUpload({

View File

@@ -112,6 +112,7 @@ function s3StorageInternal({ acl, bucket, config = {} }: S3StorageOptions): Adap
}
return {
name: 's3',
generateURL: getGenerateURL({ bucket, config }),
handleDelete: getHandleDelete({ bucket, getStorageClient }),
handleUpload: getHandleUpload({

View File

@@ -136,6 +136,7 @@ function vercelBlobStorageInternal(
return ({ collection, prefix }): GeneratedAdapter => {
const { access, addRandomSuffix, baseUrl, cacheControlMaxAge, token } = options
return {
name: 'vercel-blob',
generateURL: getGenerateUrl({ baseUrl, prefix }),
handleDelete: getHandleDelete({ baseUrl, prefix, token: options.token }),
handleUpload: getHandleUpload({