Skip to content

Commit

Permalink
fix(endUser): make email optional (#3159)
Browse files Browse the repository at this point in the history
## Changes

All according to plan 🫣

Fixes
https://linear.app/nango/issue/NAN-2371/end-user-change-email-to-be-optional

- Make email optional 
Some customers do not have email in their system and even if they could
put garbage it's confusing enough.
  • Loading branch information
bodinsamuel authored Dec 12, 2024
1 parent a3035df commit 5cc5639
Show file tree
Hide file tree
Showing 15 changed files with 65 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ api.post('/sessionToken', (req, res) => {
const res = await nango.createConnectSession({
end_user: {
id: '<END-USER-ID>',
email: '<END-USER-EMAIL>',
email: '<OPTIONAL-END-USER-EMAIL>',
display_name: '<OPTIONAL-END-USER-DISPLAY-NAME>',
},
organization: {
Expand Down
4 changes: 2 additions & 2 deletions docs-v2/reference/sdks/node.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,7 @@ const { data } = await nango.createConnectSession({
<ResponseField name="id" type="string" required>
The unique identifier for the end user.
</ResponseField>
<ResponseField name="email" type="string" required>
<ResponseField name="email" type="string">
The email address of the end user.
</ResponseField>
<ResponseField name="display_name" type="string">
Expand Down Expand Up @@ -1304,7 +1304,7 @@ const { data } = await nango.createReconnectSession({
<ResponseField name="id" type="string" required>
The unique identifier for the end user.
</ResponseField>
<ResponseField name="email" type="string" required>
<ResponseField name="email" type="string">
The email address of the end user.
</ResponseField>
<ResponseField name="display_name" type="string">
Expand Down
1 change: 0 additions & 1 deletion docs-v2/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2156,7 +2156,6 @@ components:
type: object
required:
- id
- email
properties:
id:
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ const path = require('node:path');
const fs = require('node:fs');
const yaml = require('js-yaml');

/**
* @param {import('knex').Knex} knex
*/
exports.up = async function (knex) {
let providers;
const providersPath = path.join(__dirname, '..', '..', '..', 'shared', 'providers.yaml');
try {
providers = yaml.load(fs.readFileSync(providersPath, 'utf8'));
} catch (e) {
} catch (err) {
console.error(
`Warning: Failed to load providers.yaml. Skipping migration. Missing fields on existing integrations will not show warnings in the dashboard until they are saved again. Underlying error: ${e.message}. `
`Warning: Failed to load providers.yaml. Skipping migration. Missing fields on existing integrations will not show warnings in the dashboard until they are saved again. Underlying error: ${err.message}. `
);
return;
}
Expand Down Expand Up @@ -55,6 +58,9 @@ exports.up = async function (knex) {
.update({ missing_fields: knex.raw("array_append(missing_fields, 'app_link')") });
};

/**
* @param {import('knex').Knex} knex
*/
exports.down = function () {
// do nothing
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @param {import('knex').Knex} knex
*/
exports.up = async function (knex) {
await knex.raw(`ALTER TABLE "end_users" ALTER COLUMN "email" DROP NOT NULL`);
};

/**
* @param {import('knex').Knex} knex
*/
exports.down = async function (knex) {
await knex.raw(`ALTER TABLE "end_users" ALTER COLUMN "email" SET NOT NULL;`);
};
6 changes: 4 additions & 2 deletions packages/server/lib/controllers/connect/getSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ export const getConnectSession = asyncWrapper<GetConnectSession>(async (req, res
const response: GetConnectSession['Success'] = {
data: {
end_user: {
id: endUser.endUserId,
email: endUser.email
id: endUser.endUserId
}
}
};
if (endUser.displayName) {
response.data.end_user.display_name = endUser.displayName;
}
if (endUser.email) {
response.data.end_user.email = endUser.email;
}
if (endUser.organization) {
response.data.organization = {
id: endUser.organization.organizationId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe(`POST ${endpoint}`, () => {
expect(res.res.status).toBe(400);
});

it('should fail if no endUserId or email', async () => {
it('should fail if no endUserId', async () => {
const res = await api.fetch(endpoint, {
method: 'POST',
token: seed.env.secret_key,
Expand All @@ -62,10 +62,7 @@ describe(`POST ${endpoint}`, () => {
expect(res.json).toStrictEqual({
error: {
code: 'invalid_body',
errors: [
{ code: 'invalid_type', message: 'Required', path: ['end_user', 'id'] },
{ code: 'invalid_type', message: 'Required', path: ['end_user', 'email'] }
]
errors: [{ code: 'invalid_type', message: 'Required', path: ['end_user', 'id'] }]
}
});
expect(res.res.status).toBe(400);
Expand Down
2 changes: 1 addition & 1 deletion packages/server/lib/controllers/connect/postSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const bodySchema = z
end_user: z
.object({
id: z.string().max(255).min(1),
email: z.string().email().min(5),
email: z.string().email().min(5).optional(),
display_name: z.string().max(255).optional()
})
.strict(),
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/lib/services/endUser.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export async function upsertEndUser(

const createdEndUser = await createEndUser(db, {
endUserId: endUserPayload.id,
email: endUserPayload.email,
email: endUserPayload.email || null,
displayName: endUserPayload.display_name || null,
organization: organization?.id
? {
Expand Down Expand Up @@ -229,7 +229,7 @@ export async function upsertEndUser(
endUserId: endUser.endUserId,
accountId: account.id,
environmentId: environment.id,
email: endUserPayload.email,
email: endUserPayload.email || null,
displayName: endUserPayload.display_name || null,
organization: organization?.id
? {
Expand Down
2 changes: 1 addition & 1 deletion packages/types/lib/connect/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface ConnectSessionInput {
| undefined;
end_user: {
id: string;
email: string;
email?: string | undefined;
display_name?: string | undefined;
};
organization?:
Expand Down
6 changes: 3 additions & 3 deletions packages/types/lib/endUser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export interface EndUser {
readonly endUserId: string;
readonly accountId: number;
readonly environmentId: number;
readonly email: string;
readonly email: string | null;
readonly displayName?: string | null;
readonly organization?: {
readonly organizationId: string;
Expand All @@ -18,7 +18,7 @@ export interface DBEndUser {
readonly end_user_id: string;
readonly account_id: number;
readonly environment_id: number;
readonly email: string;
readonly email: string | null;
readonly display_name: string | null;
readonly organization_id: string | null;
readonly organization_display_name: string | null;
Expand All @@ -30,7 +30,7 @@ export type DBInsertEndUser = Omit<DBEndUser, 'id' | 'created_at' | 'updated_at'
export interface ApiEndUser {
id: string;
displayName: string | null;
email: string;
email: string | null;
organization: {
id: string;
displayName: string | null;
Expand Down
13 changes: 4 additions & 9 deletions packages/webapp/src/pages/Connection/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { CopyText } from '../../components/CopyText';
import { SimpleTooltip } from '../../components/SimpleTooltip';
import { Helmet } from 'react-helmet';
import { ErrorPageComponent } from '../../components/ErrorComponent';
import { EndUserProfile } from './components/EndUserProfile';
import { getConnectionDisplayName } from '../../utils/endUser';

const defaultFilter = ['all'];
const filterErrors = [
Expand Down Expand Up @@ -58,18 +60,11 @@ const columns: ColumnDef<ApiConnectionSimple>[] = [
<div className="flex gap-3 items-center">
<AvatarOrganization
email={data.endUser?.email ? data.endUser.email : null}
displayName={data.endUser ? data.endUser.displayName || data.endUser.email : data.connection_id}
displayName={getConnectionDisplayName({ endUser: data.endUser, connectionId: data.connection_id })}
/>

{data.endUser ? (
<div className="flex flex-col overflow-hidden">
<div className="text-white break-words break-all truncate">{data.endUser.email}</div>

<div className="text-dark-500 text-xs font-code flex gap-2">
{data.endUser.displayName && <span>{data.endUser.displayName}</span>}
{data.endUser.organization?.displayName && <span>({data.endUser.organization?.displayName})</span>}
</div>
</div>
<EndUserProfile endUser={data.endUser} connectionId={data.connection_id} />
) : (
<span className="break-words break-all truncate">{data.connection_id}</span>
)}
Expand Down
15 changes: 4 additions & 11 deletions packages/webapp/src/pages/Connection/Show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { AvatarOrganization } from '../../components/AvatarCustom';
import { IconTrash } from '@tabler/icons-react';
import { useToast } from '../../hooks/useToast';
import { useListIntegration } from '../../hooks/useIntegration';
import { EndUserProfile } from './components/EndUserProfile';
import { getConnectionDisplayName } from '../../utils/endUser';

export enum Tabs {
Syncs,
Expand Down Expand Up @@ -177,24 +179,15 @@ export const ConnectionShow: React.FC = () => {
<AvatarOrganization
size={'sm'}
email={connection.endUser?.email ? connection.endUser.email : null}
displayName={
connection.endUser ? connection.endUser.displayName || connection.endUser.email : connection.connection.connection_id
}
displayName={getConnectionDisplayName({ endUser: connection.endUser, connectionId: connection.connection.connection_id })}
/>
</div>
</div>

<div className="mt-3">
<span className="font-semibold tracking-tight text-gray-400">Connection</span>
{connection.endUser ? (
<div className="flex flex-col overflow-hidden">
<h2 className="text-3xl font-semibold tracking-tight text-white break-all -mt-2">{connection.endUser.email}</h2>

<div className="text-dark-500 text-xs font-code flex gap-2">
{connection.endUser.displayName && <span>{connection.endUser.displayName}</span>}
{connection.endUser.organization?.displayName && <span>({connection.endUser.organization?.displayName})</span>}
</div>
</div>
<EndUserProfile endUser={connection.endUser} connectionId={connection.connection.connection_id} />
) : (
<h2 className="text-3xl font-semibold tracking-tight text-white break-all -mt-2">{connectionId}</h2>
)}
Expand Down
15 changes: 15 additions & 0 deletions packages/webapp/src/pages/Connection/components/EndUserProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ApiEndUser } from '@nangohq/types';

export const EndUserProfile: React.FC<{ endUser: ApiEndUser; connectionId: string }> = ({ endUser, connectionId }) => {
return (
<div className="flex flex-col overflow-hidden">
<div className="text-white break-words break-all truncate">{endUser.email ?? endUser.displayName ?? connectionId}</div>

<div className="text-dark-500 text-xs font-code flex gap-2">
{endUser.email && endUser.displayName && <span>{endUser.displayName}</span>}
{!endUser.email && endUser.displayName && <span>{connectionId}</span>}
{endUser.organization?.displayName && <span>({endUser.organization?.displayName})</span>}
</div>
</div>
);
};
5 changes: 5 additions & 0 deletions packages/webapp/src/utils/endUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ApiEndUser } from '@nangohq/types';

export function getConnectionDisplayName({ endUser, connectionId }: { endUser?: ApiEndUser | null; connectionId: string }): string {
return endUser?.displayName || endUser?.email || connectionId;
}

0 comments on commit 5cc5639

Please sign in to comment.